File indexing completed on 2025-01-05 03:59:26
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2008 Torsten Rahn <tackat@kde.org> 0004 // 0005 0006 #include "OverviewMap.h" 0007 0008 #include <QRect> 0009 #include <QStringList> 0010 #include <QCursor> 0011 #include <QMouseEvent> 0012 #include <QPainter> 0013 #include <QFileDialog> 0014 #include <QHBoxLayout> 0015 #include <QColorDialog> 0016 0017 #include <klocalizedstring.h> 0018 0019 #include "MarbleDirs.h" 0020 #include "MarbleModel.h" 0021 #include "GeoDataPoint.h" 0022 #include "ViewportParams.h" 0023 #include "MarbleWidget.h" 0024 #include "Planet.h" 0025 #include "PlanetFactory.h" 0026 #include "ui_OverviewMapConfigWidget.h" 0027 0028 namespace Marble 0029 { 0030 0031 OverviewMap::OverviewMap() 0032 : AbstractFloatItem( nullptr ), 0033 ui_configWidget( nullptr ), 0034 m_configDialog( nullptr ), 0035 m_mapChanged( false ) 0036 { 0037 } 0038 0039 OverviewMap::OverviewMap( const MarbleModel *marbleModel ) 0040 : AbstractFloatItem( marbleModel, QPointF( 10.5, 10.5 ), QSizeF( 166.0, 86.0 ) ), 0041 m_target(), 0042 m_planetID( PlanetFactory::planetList() ), 0043 m_defaultSize( AbstractFloatItem::size() ), 0044 ui_configWidget( nullptr ), 0045 m_configDialog( nullptr ), 0046 m_mapChanged( false ) 0047 { 0048 // cache is no needed because: 0049 // (1) the SVG overview map is already rendered and stored in m_worldmap pixmap 0050 // (2) bounding box and location dot keep changing during navigation 0051 setCacheMode( NoCache ); 0052 connect( this, SIGNAL(settingsChanged(QString)), 0053 this, SLOT(updateSettings()) ); 0054 0055 restoreDefaultSettings(); 0056 } 0057 0058 OverviewMap::~OverviewMap() 0059 { 0060 QHash<QString, QSvgWidget *>::const_iterator pos = m_svgWidgets.constBegin(); 0061 QHash<QString, QSvgWidget *>::const_iterator const end = m_svgWidgets.constEnd(); 0062 for (; pos != end; ++pos ) { 0063 delete pos.value(); 0064 } 0065 } 0066 0067 QStringList OverviewMap::backendTypes() const 0068 { 0069 return QStringList(QStringLiteral("overviewmap")); 0070 } 0071 0072 QString OverviewMap::name() const 0073 { 0074 return i18n("Overview Map"); 0075 } 0076 0077 QString OverviewMap::guiString() const 0078 { 0079 return i18n("&Overview Map"); 0080 } 0081 0082 QString OverviewMap::nameId() const 0083 { 0084 return QStringLiteral("overviewmap"); 0085 } 0086 0087 QString OverviewMap::version() const 0088 { 0089 return QStringLiteral("1.0"); 0090 } 0091 0092 QString OverviewMap::description() const 0093 { 0094 return i18n("A plugin to add a float item that provides an overview map."); 0095 } 0096 0097 QString OverviewMap::copyrightYears() const 0098 { 0099 return QStringLiteral("2008"); 0100 } 0101 0102 QVector<PluginAuthor> OverviewMap::pluginAuthors() const 0103 { 0104 return QVector<PluginAuthor>() 0105 << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org")); 0106 } 0107 0108 QIcon OverviewMap::icon () const 0109 { 0110 return QIcon::fromTheme(QStringLiteral("map-globe")); 0111 } 0112 0113 QDialog *OverviewMap::configDialog() 0114 { 0115 if ( !m_configDialog ) { 0116 // Initializing configuration dialog 0117 m_configDialog = new QDialog(); 0118 ui_configWidget = new Ui::OverviewMapConfigWidget; 0119 ui_configWidget->setupUi( m_configDialog ); 0120 ui_configWidget->label_3->setVisible(false); 0121 ui_configWidget->m_planetComboBox->setVisible(false); 0122 ui_configWidget->m_fileChooserButton->setVisible(false); 0123 ui_configWidget->m_tableWidget->setVisible(false); 0124 0125 for( int i = 0; i < m_planetID.size(); ++i ) { 0126 ui_configWidget->m_planetComboBox->addItem( PlanetFactory::localizedName(m_planetID.value( i ) ) ); 0127 } 0128 ui_configWidget->m_planetComboBox->setCurrentIndex( 2 ); 0129 readSettings(); 0130 loadMapSuggestions(); 0131 connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), 0132 SLOT(writeSettings()) ); 0133 connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()), 0134 SLOT(readSettings()) ); 0135 connect( ui_configWidget->m_buttonBox->button( QDialogButtonBox::Reset ), SIGNAL(clicked()), 0136 SLOT(restoreDefaultSettings()) ); 0137 QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply ); 0138 connect( applyButton, SIGNAL(clicked()), 0139 SLOT(writeSettings()) ); 0140 connect( ui_configWidget->m_fileChooserButton, SIGNAL(clicked()), 0141 SLOT(chooseCustomMap()) ); 0142 connect( ui_configWidget->m_widthBox, SIGNAL(valueChanged(int)), 0143 SLOT(synchronizeSpinboxes()) ); 0144 connect( ui_configWidget->m_heightBox, SIGNAL(valueChanged(int)), 0145 SLOT(synchronizeSpinboxes()) ); 0146 connect( ui_configWidget->m_planetComboBox, SIGNAL(currentIndexChanged(int)), 0147 SLOT(showCurrentPlanetPreview()) ); 0148 connect( ui_configWidget->m_colorChooserButton, SIGNAL(clicked()), 0149 SLOT(choosePositionIndicatorColor()) ); 0150 connect( ui_configWidget->m_tableWidget, SIGNAL(cellClicked(int,int)), 0151 SLOT(useMapSuggestion(int)) ); 0152 } 0153 return m_configDialog; 0154 } 0155 0156 void OverviewMap::initialize () 0157 { 0158 } 0159 0160 bool OverviewMap::isInitialized () const 0161 { 0162 return true; 0163 } 0164 0165 void OverviewMap::setProjection( const ViewportParams *viewport ) 0166 { 0167 GeoDataLatLonAltBox latLonAltBox = viewport->latLonAltBox( QRect( QPoint( 0, 0 ), viewport->size() ) ); 0168 const qreal centerLon = viewport->centerLongitude(); 0169 const qreal centerLat = viewport->centerLatitude(); 0170 QString target = marbleModel()->planetId(); 0171 0172 if ( target != m_target ) { 0173 changeBackground( target ); 0174 m_target = target; 0175 update(); 0176 } 0177 0178 if ( !( m_latLonAltBox == latLonAltBox 0179 && m_centerLon == centerLon 0180 && m_centerLat == centerLat ) ) 0181 { 0182 m_latLonAltBox = latLonAltBox; 0183 m_centerLon = centerLon; 0184 m_centerLat = centerLat; 0185 update(); 0186 } 0187 0188 AbstractFloatItem::setProjection( viewport ); 0189 } 0190 0191 void OverviewMap::paintContent( QPainter *painter ) 0192 { 0193 painter->save(); 0194 0195 QRectF mapRect( contentRect() ); 0196 0197 if ( m_svgobj.isValid() ) { 0198 // Rerender worldmap pixmap if the size or map has changed 0199 if ( m_worldmap.size() != mapRect.size().toSize() || m_mapChanged ) { 0200 m_mapChanged = false; 0201 m_worldmap = QPixmap( mapRect.size().toSize() ); 0202 m_worldmap.fill( Qt::transparent ); 0203 QPainter mapPainter; 0204 mapPainter.begin( &m_worldmap ); 0205 mapPainter.setViewport( m_worldmap.rect() ); 0206 m_svgobj.render( &mapPainter ); 0207 mapPainter.end(); 0208 } 0209 0210 painter->drawPixmap( QPoint( 0, 0 ), m_worldmap ); 0211 } 0212 else { 0213 painter->setPen( QPen( Qt::DashLine ) ); 0214 painter->drawRect( QRectF( QPoint( 0, 0 ), mapRect.size().toSize() ) ); 0215 0216 for ( int y = 1; y < 4; ++y ) { 0217 if ( y == 2 ) { 0218 painter->setPen( QPen( Qt::DashLine ) ); 0219 } 0220 else { 0221 painter->setPen( QPen( Qt::DotLine ) ); 0222 } 0223 0224 painter->drawLine( 0.0, 0.25 * y * mapRect.height(), 0225 mapRect.width(), 0.25 * y * mapRect.height() ); 0226 } 0227 for ( int x = 1; x < 8; ++x ) { 0228 if ( x == 4 ) { 0229 painter->setPen( QPen( Qt::DashLine ) ); 0230 } 0231 else { 0232 painter->setPen( QPen( Qt::DotLine ) ); 0233 } 0234 0235 painter->drawLine( 0.125 * x * mapRect.width(), 0, 0236 0.125 * x * mapRect.width(), mapRect.height() ); 0237 } 0238 } 0239 0240 // Now draw the latitude longitude bounding box 0241 qreal xWest = mapRect.width() / 2.0 0242 + mapRect.width() / ( 2.0 * M_PI ) * m_latLonAltBox.west(); 0243 qreal xEast = mapRect.width() / 2.0 0244 + mapRect.width() / ( 2.0 * M_PI ) * m_latLonAltBox.east(); 0245 qreal xNorth = mapRect.height() / 2.0 0246 - mapRect.height() / M_PI * m_latLonAltBox.north(); 0247 qreal xSouth = mapRect.height() / 2.0 0248 - mapRect.height() / M_PI * m_latLonAltBox.south(); 0249 0250 qreal lon = m_centerLon; 0251 qreal lat = m_centerLat; 0252 GeoDataCoordinates::normalizeLonLat( lon, lat ); 0253 qreal x = mapRect.width() / 2.0 + mapRect.width() / ( 2.0 * M_PI ) * lon; 0254 qreal y = mapRect.height() / 2.0 - mapRect.height() / M_PI * lat; 0255 0256 painter->setPen( QPen( Qt::white ) ); 0257 painter->setBrush( QBrush( Qt::transparent ) ); 0258 painter->setRenderHint( QPainter::Antialiasing, false ); 0259 0260 qreal boxWidth = xEast - xWest; 0261 qreal boxHeight = xSouth - xNorth; 0262 0263 qreal minBoxSize = 2.0; 0264 if ( boxHeight < minBoxSize ) boxHeight = minBoxSize; 0265 0266 if ( m_latLonAltBox.west() <= m_latLonAltBox.east() ) { 0267 // Make sure the latLonBox is still visible 0268 if ( boxWidth < minBoxSize ) boxWidth = minBoxSize; 0269 0270 painter->drawRect( QRectF( xWest, xNorth, boxWidth, boxHeight ) ); 0271 } 0272 else { 0273 // If the dateline is shown in the viewport and if the poles are not 0274 // then there are two boxes that represent the latLonBox of the view. 0275 0276 boxWidth = xEast; 0277 0278 // Make sure the latLonBox is still visible 0279 if ( boxWidth < minBoxSize ) boxWidth = minBoxSize; 0280 0281 painter->drawRect( QRectF( 0, xNorth, boxWidth, boxHeight ) ); 0282 0283 boxWidth = mapRect.width() - xWest; 0284 0285 // Make sure the latLonBox is still visible 0286 if ( boxWidth < minBoxSize ) boxWidth = minBoxSize; 0287 0288 painter->drawRect( QRectF( xWest, xNorth, boxWidth, boxHeight ) ); 0289 } 0290 0291 painter->setPen( QPen( m_posColor ) ); 0292 painter->setBrush( QBrush( m_posColor ) ); 0293 0294 qreal circleRadius = 2.5; 0295 painter->setRenderHint( QPainter::Antialiasing, true ); 0296 painter->drawEllipse( QRectF( x - circleRadius, y - circleRadius , 2 * circleRadius, 2 * circleRadius ) ); 0297 0298 painter->restore(); 0299 } 0300 0301 QHash<QString,QVariant> OverviewMap::settings() const 0302 { 0303 QHash<QString, QVariant> result = AbstractFloatItem::settings(); 0304 0305 typedef QHash<QString, QVariant>::ConstIterator Iterator; 0306 Iterator end = m_settings.constEnd(); 0307 for ( Iterator iter = m_settings.constBegin(); iter != end; ++iter ) { 0308 result.insert( iter.key(), iter.value() ); 0309 } 0310 0311 return result; 0312 } 0313 0314 void OverviewMap::setSettings( const QHash<QString,QVariant> &settings ) 0315 { 0316 AbstractFloatItem::setSettings( settings ); 0317 0318 m_settings.insert(QStringLiteral("width"), settings.value(QStringLiteral("width"), m_defaultSize.toSize().width())); 0319 m_settings.insert(QStringLiteral("height"), settings.value(QStringLiteral("height"), m_defaultSize.toSize().height())); 0320 0321 for ( const QString& planet: PlanetFactory::planetList() ) { 0322 QString mapFile = MarbleDirs::path(QLatin1String("svg/") + planet + QLatin1String("map.svg")); 0323 0324 if (planet == QLatin1String("moon")) { 0325 mapFile = MarbleDirs::path(QStringLiteral("svg/lunarmap.svg")); 0326 } 0327 else if (planet == QLatin1String("earth") || mapFile.isEmpty()) { 0328 mapFile = MarbleDirs::path(QStringLiteral("svg/worldmap.svg")); 0329 } 0330 0331 const QString id = QLatin1String("path_") + planet; 0332 m_settings.insert(id, settings.value(id, mapFile)); 0333 } 0334 0335 m_settings.insert(QStringLiteral("posColor"), settings.value(QStringLiteral("posColor"), QColor(Qt::white).name())); 0336 0337 m_target.clear(); // FIXME: forces execution of changeBackground() in changeViewport() 0338 0339 readSettings(); 0340 Q_EMIT settingsChanged( nameId() ); 0341 } 0342 0343 void OverviewMap::readSettings() 0344 { 0345 if ( !m_configDialog ) { 0346 return; 0347 } 0348 0349 ui_configWidget->m_widthBox->setValue( m_settings.value(QStringLiteral("width")).toInt() ); 0350 ui_configWidget->m_heightBox->setValue( m_settings.value(QStringLiteral("height")).toInt() ); 0351 QPalette palette = ui_configWidget->m_colorChooserButton->palette(); 0352 palette.setColor(QPalette::Button, QColor(m_settings.value(QStringLiteral("posColor")).toString())); 0353 ui_configWidget->m_colorChooserButton->setPalette( palette ); 0354 } 0355 0356 void OverviewMap::writeSettings() 0357 { 0358 if ( !m_configDialog ) { 0359 return; 0360 } 0361 0362 m_settings.insert(QStringLiteral("width"), contentRect().width()); 0363 m_settings.insert(QStringLiteral("height"), contentRect().height()); 0364 0365 QStringList const planets = PlanetFactory::planetList(); 0366 for( const QString &planet: planets ) { 0367 m_settings.insert(QLatin1String("path_") + planet, m_svgPaths[planet]); 0368 } 0369 0370 m_settings.insert(QStringLiteral("posColor"), m_posColor.name()); 0371 0372 Q_EMIT settingsChanged( nameId() ); 0373 } 0374 0375 void OverviewMap::updateSettings() 0376 { 0377 QStringList const planets = PlanetFactory::planetList(); 0378 for( const QString &planet: planets ) { 0379 m_svgPaths.insert(planet, m_settings.value(QLatin1String("path_") + planet, QString()).toString()); 0380 } 0381 0382 m_posColor = QColor(m_settings.value(QStringLiteral("posColor")).toString()); 0383 loadPlanetMaps(); 0384 0385 if ( !m_configDialog ) { 0386 return; 0387 } 0388 0389 setCurrentWidget( m_svgWidgets[m_planetID[2]] ); 0390 showCurrentPlanetPreview(); 0391 setContentSize( QSizeF( ui_configWidget->m_widthBox->value(), ui_configWidget->m_heightBox->value() ) ); 0392 } 0393 0394 bool OverviewMap::eventFilter( QObject *object, QEvent *e ) 0395 { 0396 if ( !enabled() || !visible() ) { 0397 return false; 0398 } 0399 0400 MarbleWidget *widget = dynamic_cast<MarbleWidget*>(object); 0401 if ( !widget ) { 0402 return AbstractFloatItem::eventFilter(object,e); 0403 } 0404 0405 if ( e->type() == QEvent::MouseButtonDblClick || e->type() == QEvent::MouseMove ) { 0406 QMouseEvent *event = static_cast<QMouseEvent*>(e); 0407 QRectF floatItemRect = QRectF( positivePosition(), size() ); 0408 0409 bool cursorAboveFloatItem(false); 0410 if ( floatItemRect.contains(event->pos()) ) { 0411 cursorAboveFloatItem = true; 0412 0413 // Double click triggers recentering the map at the specified position 0414 if ( e->type() == QEvent::MouseButtonDblClick ) { 0415 QRectF mapRect( contentRect() ); 0416 QPointF pos = event->pos() - floatItemRect.topLeft() 0417 - QPointF(padding(),padding()); 0418 0419 qreal lon = ( pos.x() - mapRect.width() / 2.0 ) / mapRect.width() * 360.0 ; 0420 qreal lat = ( mapRect.height() / 2.0 - pos.y() ) / mapRect.height() * 180.0; 0421 widget->centerOn(lon,lat,true); 0422 0423 return true; 0424 } 0425 } 0426 0427 if ( cursorAboveFloatItem && e->type() == QEvent::MouseMove 0428 && !(event->buttons() & Qt::LeftButton) ) 0429 { 0430 // Cross hair cursor when moving above the float item without pressing a button 0431 widget->setCursor(QCursor(Qt::CrossCursor)); 0432 return true; 0433 } 0434 } 0435 0436 return AbstractFloatItem::eventFilter(object,e); 0437 } 0438 0439 void OverviewMap::changeBackground( const QString& target ) 0440 { 0441 m_svgobj.load( m_svgPaths[target] ); 0442 m_mapChanged = true; 0443 } 0444 0445 QSvgWidget *OverviewMap::currentWidget() const 0446 { 0447 return m_svgWidgets[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]]; 0448 } 0449 0450 void OverviewMap::setCurrentWidget( QSvgWidget *widget ) 0451 { 0452 m_svgWidgets[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = widget; 0453 if( m_target == m_planetID[ui_configWidget->m_planetComboBox->currentIndex()] ) { 0454 changeBackground( m_target ); 0455 } 0456 } 0457 0458 void OverviewMap::loadPlanetMaps() 0459 { 0460 for( const QString& planet: m_planetID ) { 0461 if ( m_svgWidgets.contains( planet) ) { 0462 m_svgWidgets[planet]->load( m_svgPaths[planet] ); 0463 } else { 0464 m_svgWidgets[planet] = new QSvgWidget( m_svgPaths[planet] ); 0465 } 0466 } 0467 } 0468 0469 void OverviewMap::loadMapSuggestions() 0470 { 0471 QStringList paths = QDir(MarbleDirs::pluginPath(QString())).entryList(QStringList(QLatin1String("*.svg")), QDir::Files | QDir::NoDotAndDotDot); 0472 for( int i = 0; i < paths.size(); ++i ) { 0473 paths[i] = MarbleDirs::pluginPath(QString()) + QLatin1Char('/') + paths[i]; 0474 } 0475 paths << MarbleDirs::path(QStringLiteral("svg/worldmap.svg")) << MarbleDirs::path(QStringLiteral("svg/lunarmap.svg")); 0476 ui_configWidget->m_tableWidget->setRowCount( paths.size() ); 0477 for( int i = 0; i < paths.size(); ++i ) { 0478 ui_configWidget->m_tableWidget->setCellWidget( i, 0, new QSvgWidget( paths[i] ) ); 0479 ui_configWidget->m_tableWidget->setItem( i, 1, new QTableWidgetItem( paths[i] ) ); 0480 } 0481 } 0482 0483 void OverviewMap::chooseCustomMap() 0484 { 0485 QString path = QFileDialog::getOpenFileName ( m_configDialog, i18n( "Choose Overview Map" ), QLatin1String(""), QLatin1String("SVG (*.svg)") ); 0486 if( !path.isNull() ) 0487 { 0488 ui_configWidget->m_fileChooserButton->layout()->removeWidget( currentWidget() ); 0489 delete currentWidget(); 0490 QSvgWidget *widget = new QSvgWidget( path ); 0491 setCurrentWidget( widget ); 0492 ui_configWidget->m_fileChooserButton->layout()->addWidget( widget ); 0493 m_svgPaths[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = path; 0494 } 0495 } 0496 0497 void OverviewMap::synchronizeSpinboxes() 0498 { 0499 if( sender() == ui_configWidget->m_widthBox ) { 0500 ui_configWidget->m_heightBox->setValue( ui_configWidget->m_widthBox->value() / 2 ); 0501 } 0502 else if( sender() == ui_configWidget->m_heightBox ) { 0503 ui_configWidget->m_widthBox->setValue( ui_configWidget->m_heightBox->value() * 2 ); 0504 } 0505 } 0506 0507 void OverviewMap::showCurrentPlanetPreview() const 0508 { 0509 delete ui_configWidget->m_fileChooserButton->layout(); 0510 ui_configWidget->m_fileChooserButton->setLayout( new QHBoxLayout() ); 0511 ui_configWidget->m_fileChooserButton->layout()->addWidget( currentWidget() ); 0512 } 0513 0514 void OverviewMap::choosePositionIndicatorColor() 0515 { 0516 QColor c = QColorDialog::getColor( m_posColor, nullptr, 0517 i18n( "Please choose the color for the position indicator" ), 0518 QColorDialog::ShowAlphaChannel ); 0519 if( c.isValid() ) 0520 { 0521 m_posColor = c; 0522 QPalette palette = ui_configWidget->m_colorChooserButton->palette(); 0523 palette.setColor( QPalette::Button, m_posColor ); 0524 ui_configWidget->m_colorChooserButton->setPalette( palette ); 0525 } 0526 } 0527 0528 void OverviewMap::useMapSuggestion( int index ) 0529 { 0530 QString path = ui_configWidget->m_tableWidget->item( index, 1 )->text(); 0531 m_svgPaths[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = path; 0532 delete currentWidget(); 0533 QSvgWidget *widget = new QSvgWidget( path ); 0534 setCurrentWidget( widget ); 0535 showCurrentPlanetPreview(); 0536 } 0537 0538 } // namespace Marble 0539 0540 #include "moc_OverviewMap.cpp"