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"