File indexing completed on 2024-05-12 15:31:10

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"