File indexing completed on 2024-11-10 03:34:57
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 "MarbleDirs.h" 0018 #include "MarbleDebug.h" 0019 #include "MarbleModel.h" 0020 #include "ui_OverviewMapConfigWidget.h" 0021 0022 #include "GeoDataPoint.h" 0023 #include "ViewportParams.h" 0024 #include "MarbleWidget.h" 0025 #include "Planet.h" 0026 #include "PlanetFactory.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 tr("Overview Map"); 0075 } 0076 0077 QString OverviewMap::guiString() const 0078 { 0079 return tr("&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 tr("This is 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(QStringLiteral(":/icons/worldmap.png")); 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 for( int i = 0; i < m_planetID.size(); ++i ) { 0121 ui_configWidget->m_planetComboBox->addItem( PlanetFactory::localizedName(m_planetID.value( i ) ) ); 0122 } 0123 ui_configWidget->m_planetComboBox->setCurrentIndex( 2 ); 0124 readSettings(); 0125 loadMapSuggestions(); 0126 connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), 0127 SLOT(writeSettings()) ); 0128 connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()), 0129 SLOT(readSettings()) ); 0130 connect( ui_configWidget->m_buttonBox->button( QDialogButtonBox::Reset ), SIGNAL(clicked()), 0131 SLOT(restoreDefaultSettings()) ); 0132 QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply ); 0133 connect( applyButton, SIGNAL(clicked()), 0134 SLOT(writeSettings()) ); 0135 connect( ui_configWidget->m_fileChooserButton, SIGNAL(clicked()), 0136 SLOT(chooseCustomMap()) ); 0137 connect( ui_configWidget->m_widthBox, SIGNAL(valueChanged(int)), 0138 SLOT(synchronizeSpinboxes()) ); 0139 connect( ui_configWidget->m_heightBox, SIGNAL(valueChanged(int)), 0140 SLOT(synchronizeSpinboxes()) ); 0141 connect( ui_configWidget->m_planetComboBox, SIGNAL(currentIndexChanged(int)), 0142 SLOT(showCurrentPlanetPreview()) ); 0143 connect( ui_configWidget->m_colorChooserButton, SIGNAL(clicked()), 0144 SLOT(choosePositionIndicatorColor()) ); 0145 connect( ui_configWidget->m_tableWidget, SIGNAL(cellClicked(int,int)), 0146 SLOT(useMapSuggestion(int)) ); 0147 } 0148 return m_configDialog; 0149 } 0150 0151 void OverviewMap::initialize () 0152 { 0153 } 0154 0155 bool OverviewMap::isInitialized () const 0156 { 0157 return true; 0158 } 0159 0160 void OverviewMap::setProjection( const ViewportParams *viewport ) 0161 { 0162 GeoDataLatLonAltBox latLonAltBox = viewport->latLonAltBox( QRect( QPoint( 0, 0 ), viewport->size() ) ); 0163 const qreal centerLon = viewport->centerLongitude(); 0164 const qreal centerLat = viewport->centerLatitude(); 0165 QString target = marbleModel()->planetId(); 0166 0167 if ( target != m_target ) { 0168 changeBackground( target ); 0169 m_target = target; 0170 update(); 0171 } 0172 0173 if ( !( m_latLonAltBox == latLonAltBox 0174 && m_centerLon == centerLon 0175 && m_centerLat == centerLat ) ) 0176 { 0177 m_latLonAltBox = latLonAltBox; 0178 m_centerLon = centerLon; 0179 m_centerLat = centerLat; 0180 update(); 0181 } 0182 0183 AbstractFloatItem::setProjection( viewport ); 0184 } 0185 0186 void OverviewMap::paintContent( QPainter *painter ) 0187 { 0188 painter->save(); 0189 0190 QRectF mapRect( contentRect() ); 0191 0192 if ( m_svgobj.isValid() ) { 0193 // Rerender worldmap pixmap if the size or map has changed 0194 if ( m_worldmap.size() != mapRect.size().toSize() || m_mapChanged ) { 0195 m_mapChanged = false; 0196 m_worldmap = QPixmap( mapRect.size().toSize() ); 0197 m_worldmap.fill( Qt::transparent ); 0198 QPainter mapPainter; 0199 mapPainter.begin( &m_worldmap ); 0200 mapPainter.setViewport( m_worldmap.rect() ); 0201 m_svgobj.render( &mapPainter ); 0202 mapPainter.end(); 0203 } 0204 0205 painter->drawPixmap( QPoint( 0, 0 ), m_worldmap ); 0206 } 0207 else { 0208 painter->setPen( QPen( Qt::DashLine ) ); 0209 painter->drawRect( QRectF( QPoint( 0, 0 ), mapRect.size().toSize() ) ); 0210 0211 for ( int y = 1; y < 4; ++y ) { 0212 if ( y == 2 ) { 0213 painter->setPen( QPen( Qt::DashLine ) ); 0214 } 0215 else { 0216 painter->setPen( QPen( Qt::DotLine ) ); 0217 } 0218 0219 painter->drawLine( 0.0, 0.25 * y * mapRect.height(), 0220 mapRect.width(), 0.25 * y * mapRect.height() ); 0221 } 0222 for ( int x = 1; x < 8; ++x ) { 0223 if ( x == 4 ) { 0224 painter->setPen( QPen( Qt::DashLine ) ); 0225 } 0226 else { 0227 painter->setPen( QPen( Qt::DotLine ) ); 0228 } 0229 0230 painter->drawLine( 0.125 * x * mapRect.width(), 0, 0231 0.125 * x * mapRect.width(), mapRect.height() ); 0232 } 0233 } 0234 0235 // Now draw the latitude longitude bounding box 0236 qreal xWest = mapRect.width() / 2.0 0237 + mapRect.width() / ( 2.0 * M_PI ) * m_latLonAltBox.west(); 0238 qreal xEast = mapRect.width() / 2.0 0239 + mapRect.width() / ( 2.0 * M_PI ) * m_latLonAltBox.east(); 0240 qreal xNorth = mapRect.height() / 2.0 0241 - mapRect.height() / M_PI * m_latLonAltBox.north(); 0242 qreal xSouth = mapRect.height() / 2.0 0243 - mapRect.height() / M_PI * m_latLonAltBox.south(); 0244 0245 qreal lon = m_centerLon; 0246 qreal lat = m_centerLat; 0247 GeoDataCoordinates::normalizeLonLat( lon, lat ); 0248 qreal x = mapRect.width() / 2.0 + mapRect.width() / ( 2.0 * M_PI ) * lon; 0249 qreal y = mapRect.height() / 2.0 - mapRect.height() / M_PI * lat; 0250 0251 painter->setPen( QPen( Qt::white ) ); 0252 painter->setBrush( QBrush( Qt::transparent ) ); 0253 painter->setRenderHint( QPainter::Antialiasing, false ); 0254 0255 qreal boxWidth = xEast - xWest; 0256 qreal boxHeight = xSouth - xNorth; 0257 0258 qreal minBoxSize = 2.0; 0259 if ( boxHeight < minBoxSize ) boxHeight = minBoxSize; 0260 0261 if ( m_latLonAltBox.west() <= m_latLonAltBox.east() ) { 0262 // Make sure the latLonBox is still visible 0263 if ( boxWidth < minBoxSize ) boxWidth = minBoxSize; 0264 0265 painter->drawRect( QRectF( xWest, xNorth, boxWidth, boxHeight ) ); 0266 } 0267 else { 0268 // If the dateline is shown in the viewport and if the poles are not 0269 // then there are two boxes that represent the latLonBox of the view. 0270 0271 boxWidth = xEast; 0272 0273 // Make sure the latLonBox is still visible 0274 if ( boxWidth < minBoxSize ) boxWidth = minBoxSize; 0275 0276 painter->drawRect( QRectF( 0, xNorth, boxWidth, boxHeight ) ); 0277 0278 boxWidth = mapRect.width() - xWest; 0279 0280 // Make sure the latLonBox is still visible 0281 if ( boxWidth < minBoxSize ) boxWidth = minBoxSize; 0282 0283 painter->drawRect( QRectF( xWest, xNorth, boxWidth, boxHeight ) ); 0284 } 0285 0286 painter->setPen( QPen( m_posColor ) ); 0287 painter->setBrush( QBrush( m_posColor ) ); 0288 0289 qreal circleRadius = 2.5; 0290 painter->setRenderHint( QPainter::Antialiasing, true ); 0291 painter->drawEllipse( QRectF( x - circleRadius, y - circleRadius , 2 * circleRadius, 2 * circleRadius ) ); 0292 0293 painter->restore(); 0294 } 0295 0296 QHash<QString,QVariant> OverviewMap::settings() const 0297 { 0298 QHash<QString, QVariant> result = AbstractFloatItem::settings(); 0299 0300 typedef QHash<QString, QVariant>::ConstIterator Iterator; 0301 Iterator end = m_settings.constEnd(); 0302 for ( Iterator iter = m_settings.constBegin(); iter != end; ++iter ) { 0303 result.insert( iter.key(), iter.value() ); 0304 } 0305 0306 return result; 0307 } 0308 0309 void OverviewMap::setSettings( const QHash<QString,QVariant> &settings ) 0310 { 0311 AbstractFloatItem::setSettings( settings ); 0312 0313 m_settings.insert(QStringLiteral("width"), settings.value(QStringLiteral("width"), m_defaultSize.toSize().width())); 0314 m_settings.insert(QStringLiteral("height"), settings.value(QStringLiteral("height"), m_defaultSize.toSize().height())); 0315 0316 for ( const QString& planet: PlanetFactory::planetList() ) { 0317 QString mapFile = MarbleDirs::path(QLatin1String("svg/") + planet + QLatin1String("map.svg")); 0318 0319 if (planet == QLatin1String("moon")) { 0320 mapFile = MarbleDirs::path(QStringLiteral("svg/lunarmap.svg")); 0321 } 0322 else if (planet == QLatin1String("earth") || mapFile.isEmpty()) { 0323 mapFile = MarbleDirs::path(QStringLiteral("svg/worldmap.svg")); 0324 } 0325 0326 const QString id = QLatin1String("path_") + planet; 0327 m_settings.insert(id, settings.value(id, mapFile)); 0328 } 0329 0330 m_settings.insert(QStringLiteral("posColor"), settings.value(QStringLiteral("posColor"), QColor(Qt::white).name())); 0331 0332 m_target.clear(); // FIXME: forces execution of changeBackground() in changeViewport() 0333 0334 readSettings(); 0335 emit settingsChanged( nameId() ); 0336 } 0337 0338 void OverviewMap::readSettings() 0339 { 0340 if ( !m_configDialog ) { 0341 return; 0342 } 0343 0344 ui_configWidget->m_widthBox->setValue( m_settings.value(QStringLiteral("width")).toInt() ); 0345 ui_configWidget->m_heightBox->setValue( m_settings.value(QStringLiteral("height")).toInt() ); 0346 QPalette palette = ui_configWidget->m_colorChooserButton->palette(); 0347 palette.setColor(QPalette::Button, QColor(m_settings.value(QStringLiteral("posColor")).toString())); 0348 ui_configWidget->m_colorChooserButton->setPalette( palette ); 0349 } 0350 0351 void OverviewMap::writeSettings() 0352 { 0353 if ( !m_configDialog ) { 0354 return; 0355 } 0356 0357 m_settings.insert(QStringLiteral("width"), contentRect().width()); 0358 m_settings.insert(QStringLiteral("height"), contentRect().height()); 0359 0360 QStringList const planets = PlanetFactory::planetList(); 0361 for( const QString &planet: planets ) { 0362 m_settings.insert(QLatin1String("path_") + planet, m_svgPaths[planet]); 0363 } 0364 0365 m_settings.insert(QStringLiteral("posColor"), m_posColor.name()); 0366 0367 emit settingsChanged( nameId() ); 0368 } 0369 0370 void OverviewMap::updateSettings() 0371 { 0372 QStringList const planets = PlanetFactory::planetList(); 0373 for( const QString &planet: planets ) { 0374 m_svgPaths.insert(planet, m_settings.value(QLatin1String("path_") + planet, QString()).toString()); 0375 } 0376 0377 m_posColor = QColor(m_settings.value(QStringLiteral("posColor")).toString()); 0378 loadPlanetMaps(); 0379 0380 if ( !m_configDialog ) { 0381 return; 0382 } 0383 0384 setCurrentWidget( m_svgWidgets[m_planetID[2]] ); 0385 showCurrentPlanetPreview(); 0386 setContentSize( QSizeF( ui_configWidget->m_widthBox->value(), ui_configWidget->m_heightBox->value() ) ); 0387 } 0388 0389 bool OverviewMap::eventFilter( QObject *object, QEvent *e ) 0390 { 0391 if ( !enabled() || !visible() ) { 0392 return false; 0393 } 0394 0395 MarbleWidget *widget = dynamic_cast<MarbleWidget*>(object); 0396 if ( !widget ) { 0397 return AbstractFloatItem::eventFilter(object,e); 0398 } 0399 0400 if ( e->type() == QEvent::MouseButtonDblClick || e->type() == QEvent::MouseMove ) { 0401 QMouseEvent *event = static_cast<QMouseEvent*>(e); 0402 QRectF floatItemRect = QRectF( positivePosition(), size() ); 0403 0404 bool cursorAboveFloatItem(false); 0405 if ( floatItemRect.contains(event->pos()) ) { 0406 cursorAboveFloatItem = true; 0407 0408 // Double click triggers recentering the map at the specified position 0409 if ( e->type() == QEvent::MouseButtonDblClick ) { 0410 QRectF mapRect( contentRect() ); 0411 QPointF pos = event->pos() - floatItemRect.topLeft() 0412 - QPointF(padding(),padding()); 0413 0414 qreal lon = ( pos.x() - mapRect.width() / 2.0 ) / mapRect.width() * 360.0 ; 0415 qreal lat = ( mapRect.height() / 2.0 - pos.y() ) / mapRect.height() * 180.0; 0416 widget->centerOn(lon,lat,true); 0417 0418 return true; 0419 } 0420 } 0421 0422 if ( cursorAboveFloatItem && e->type() == QEvent::MouseMove 0423 && !(event->buttons() & Qt::LeftButton) ) 0424 { 0425 // Cross hair cursor when moving above the float item without pressing a button 0426 widget->setCursor(QCursor(Qt::CrossCursor)); 0427 return true; 0428 } 0429 } 0430 0431 return AbstractFloatItem::eventFilter(object,e); 0432 } 0433 0434 void OverviewMap::changeBackground( const QString& target ) 0435 { 0436 m_svgobj.load( m_svgPaths[target] ); 0437 m_mapChanged = true; 0438 } 0439 0440 QSvgWidget *OverviewMap::currentWidget() const 0441 { 0442 return m_svgWidgets[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]]; 0443 } 0444 0445 void OverviewMap::setCurrentWidget( QSvgWidget *widget ) 0446 { 0447 m_svgWidgets[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = widget; 0448 if( m_target == m_planetID[ui_configWidget->m_planetComboBox->currentIndex()] ) { 0449 changeBackground( m_target ); 0450 } 0451 } 0452 0453 void OverviewMap::loadPlanetMaps() 0454 { 0455 for( const QString& planet: m_planetID ) { 0456 if ( m_svgWidgets.contains( planet) ) { 0457 m_svgWidgets[planet]->load( m_svgPaths[planet] ); 0458 } else { 0459 m_svgWidgets[planet] = new QSvgWidget( m_svgPaths[planet] ); 0460 } 0461 } 0462 } 0463 0464 void OverviewMap::loadMapSuggestions() 0465 { 0466 QStringList paths = QDir(MarbleDirs::pluginPath(QString())).entryList(QStringList("*.svg"), QDir::Files | QDir::NoDotAndDotDot); 0467 for( int i = 0; i < paths.size(); ++i ) { 0468 paths[i] = MarbleDirs::pluginPath(QString()) + QLatin1Char('/') + paths[i]; 0469 } 0470 paths << MarbleDirs::path(QStringLiteral("svg/worldmap.svg")) << MarbleDirs::path(QStringLiteral("svg/lunarmap.svg")); 0471 ui_configWidget->m_tableWidget->setRowCount( paths.size() ); 0472 for( int i = 0; i < paths.size(); ++i ) { 0473 ui_configWidget->m_tableWidget->setCellWidget( i, 0, new QSvgWidget( paths[i] ) ); 0474 ui_configWidget->m_tableWidget->setItem( i, 1, new QTableWidgetItem( paths[i] ) ); 0475 } 0476 } 0477 0478 void OverviewMap::chooseCustomMap() 0479 { 0480 QString path = QFileDialog::getOpenFileName ( m_configDialog, tr( "Choose Overview Map" ), "", "SVG (*.svg)" ); 0481 if( !path.isNull() ) 0482 { 0483 ui_configWidget->m_fileChooserButton->layout()->removeWidget( currentWidget() ); 0484 delete currentWidget(); 0485 QSvgWidget *widget = new QSvgWidget( path ); 0486 setCurrentWidget( widget ); 0487 ui_configWidget->m_fileChooserButton->layout()->addWidget( widget ); 0488 m_svgPaths[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = path; 0489 } 0490 } 0491 0492 void OverviewMap::synchronizeSpinboxes() 0493 { 0494 if( sender() == ui_configWidget->m_widthBox ) { 0495 ui_configWidget->m_heightBox->setValue( ui_configWidget->m_widthBox->value() / 2 ); 0496 } 0497 else if( sender() == ui_configWidget->m_heightBox ) { 0498 ui_configWidget->m_widthBox->setValue( ui_configWidget->m_heightBox->value() * 2 ); 0499 } 0500 } 0501 0502 void OverviewMap::showCurrentPlanetPreview() const 0503 { 0504 delete ui_configWidget->m_fileChooserButton->layout(); 0505 ui_configWidget->m_fileChooserButton->setLayout( new QHBoxLayout() ); 0506 ui_configWidget->m_fileChooserButton->layout()->addWidget( currentWidget() ); 0507 } 0508 0509 void OverviewMap::choosePositionIndicatorColor() 0510 { 0511 QColor c = QColorDialog::getColor( m_posColor, nullptr, 0512 tr( "Please choose the color for the position indicator" ), 0513 QColorDialog::ShowAlphaChannel ); 0514 if( c.isValid() ) 0515 { 0516 m_posColor = c; 0517 QPalette palette = ui_configWidget->m_colorChooserButton->palette(); 0518 palette.setColor( QPalette::Button, m_posColor ); 0519 ui_configWidget->m_colorChooserButton->setPalette( palette ); 0520 } 0521 } 0522 0523 void OverviewMap::useMapSuggestion( int index ) 0524 { 0525 QString path = ui_configWidget->m_tableWidget->item( index, 1 )->text(); 0526 m_svgPaths[m_planetID[ui_configWidget->m_planetComboBox->currentIndex()]] = path; 0527 delete currentWidget(); 0528 QSvgWidget *widget = new QSvgWidget( path ); 0529 setCurrentWidget( widget ); 0530 showCurrentPlanetPreview(); 0531 } 0532 0533 } 0534 0535 #include "moc_OverviewMap.cpp"