File indexing completed on 2024-05-05 03:50:49

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2007 Andrew Manson <g.real.ate@gmail.com>
0004 // SPDX-FileCopyrightText: 2009 Eckhart Wörner <ewoerner@kde.org>
0005 // SPDX-FileCopyrightText: 2010 Thibaut Gridel <tgridel@free.fr>
0006 // SPDX-FileCopyrightText: 2010 Daniel Marth <danielmarth@gmx.at>
0007 //
0008 
0009 #include "PositionMarker.h"
0010 
0011 #include "MarbleDebug.h"
0012 #include <QRect>
0013 #include <qmath.h>
0014 #include <QFileDialog>
0015 #include <QPushButton>
0016 #include <QColorDialog>
0017 #include <QTransform>
0018 
0019 #include <cmath>
0020 
0021 #include "ui_PositionMarkerConfigWidget.h"
0022 #include "MarbleColors.h"
0023 #include "MarbleModel.h"
0024 #include "MarbleDirs.h"
0025 #include "GeoPainter.h"
0026 #include "PositionTracking.h"
0027 #include "ViewportParams.h"
0028 #include "Planet.h"
0029 #include "GeoDataAccuracy.h"
0030 
0031 namespace Marble
0032 {
0033 
0034 const int PositionMarker::sm_defaultSizeStep = 2;
0035 const float PositionMarker::sm_resizeSteps[] = { 0.25, 0.5, 1.0, 2.0, 4.0 };
0036 const int PositionMarker::sm_numResizeSteps = sizeof( sm_resizeSteps ) / sizeof( sm_resizeSteps[0] );
0037 
0038 PositionMarker::PositionMarker( const MarbleModel *marbleModel )
0039     : RenderPlugin( marbleModel ),
0040       m_marbleModel( marbleModel ),
0041       m_isInitialized( false ),
0042       m_useCustomCursor( false ),
0043       m_defaultCursorPath(MarbleDirs::path(QStringLiteral("svg/track_turtle.svg"))),
0044       m_lastBoundingBox(),
0045       ui_configWidget( nullptr ),
0046       m_configDialog( nullptr ),
0047       m_cursorPath( m_defaultCursorPath ),
0048       m_cursorSize( 1.0 ),
0049       m_accuracyColor( Oxygen::brickRed4 ),
0050       m_trailColor( 0, 0, 255 ),
0051       m_heading( 0.0 ),
0052       m_showTrail ( false )
0053 {
0054     const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0055     m_accuracyColor.setAlpha( smallScreen ? 80 : 40 );
0056 }
0057 
0058 PositionMarker::~PositionMarker ()
0059 {
0060     delete ui_configWidget;
0061     delete m_configDialog;
0062 }
0063 
0064 QStringList PositionMarker::renderPosition() const
0065 {
0066     return QStringList(QStringLiteral("HOVERS_ABOVE_SURFACE"));
0067 }
0068 
0069 QString PositionMarker::renderPolicy() const
0070 {
0071     return QStringLiteral("ALWAYS");
0072 }
0073 
0074 QStringList PositionMarker::backendTypes() const
0075 {
0076     return QStringList(QStringLiteral("positionmarker"));
0077 }
0078 
0079 QString PositionMarker::name() const
0080 {
0081     return tr( "Position Marker" );
0082 }
0083 
0084 QString PositionMarker::guiString() const
0085 {
0086     return tr( "&Position Marker" );
0087 }
0088 
0089 QString PositionMarker::nameId() const
0090 {
0091     return QStringLiteral("positionMarker");
0092 }
0093 
0094 QString PositionMarker::version() const
0095 {
0096     return QStringLiteral("1.0");
0097 }
0098 
0099 QString PositionMarker::description() const
0100 {
0101     return tr( "draws a marker at the current position" );
0102 }
0103 
0104 QString PositionMarker::copyrightYears() const
0105 {
0106     return QStringLiteral("2009, 2010");
0107 }
0108 
0109 QVector<PluginAuthor> PositionMarker::pluginAuthors() const
0110 {
0111     return QVector<PluginAuthor>()
0112             << PluginAuthor(QStringLiteral("Andrew Manson"), QStringLiteral("g.real.ate@gmail.com"))
0113             << PluginAuthor(QStringLiteral("Eckhart Woerner"), QStringLiteral("ewoerner@kde.org"))
0114             << PluginAuthor(QStringLiteral("Thibaut Gridel"), QStringLiteral("tgridel@free.fr"))
0115             << PluginAuthor(QStringLiteral("Daniel Marth"), QStringLiteral("danielmarth@gmx.at"));
0116 }
0117 
0118 QIcon PositionMarker::icon() const
0119 {
0120     return QIcon(QStringLiteral(":/icons/positionmarker.png"));
0121 }
0122 
0123 QDialog *PositionMarker::configDialog()
0124 {
0125     if ( !m_configDialog ) {
0126         // Initializing configuration dialog
0127         m_configDialog = new QDialog();
0128         ui_configWidget = new Ui::PositionMarkerConfigWidget;
0129         ui_configWidget->setupUi( m_configDialog );
0130         ui_configWidget->m_resizeSlider->setMaximum( sm_numResizeSteps - 1 );
0131         readSettings();
0132         connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()),
0133                  SLOT(writeSettings()) );
0134         connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()),
0135                  SLOT(readSettings()) );
0136         connect( ui_configWidget->m_buttonBox->button( QDialogButtonBox::RestoreDefaults ), SIGNAL(clicked()),
0137                  SLOT(restoreDefaultSettings()) );
0138         QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply );
0139         connect( applyButton, SIGNAL(clicked()),
0140                  SLOT(writeSettings()) );
0141         connect( ui_configWidget->m_fileChooserButton, SIGNAL(clicked()),
0142                  SLOT(chooseCustomCursor()) );
0143         connect( ui_configWidget->m_resizeSlider, SIGNAL(valueChanged(int)),
0144                  SLOT(resizeCursor(int)) );
0145         connect( ui_configWidget->m_acColorChooserButton, SIGNAL(clicked()),
0146                  SLOT(chooseColor()) );
0147         connect( ui_configWidget->m_trailColorChooserButton, SIGNAL(clicked()),
0148                  SLOT(chooseColor()) );
0149     }
0150     return m_configDialog;
0151 }
0152 
0153 void PositionMarker::initialize()
0154 {
0155     if ( marbleModel() ) {
0156         connect( marbleModel()->positionTracking(), SIGNAL(gpsLocation(GeoDataCoordinates,qreal)),
0157                 this, SLOT(setPosition(GeoDataCoordinates)) );
0158         connect( marbleModel()->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)),
0159                 this, SIGNAL(repaintNeeded()) );
0160         m_isInitialized = true;
0161     }
0162     loadDefaultCursor();
0163 }
0164 
0165 bool PositionMarker::isInitialized() const
0166 {
0167     return m_isInitialized;
0168 }
0169 
0170 bool PositionMarker::render( GeoPainter *painter,
0171                            ViewportParams *viewport,
0172                            const QString& renderPos,
0173                            GeoSceneLayer * layer )
0174 {
0175     Q_UNUSED( renderPos )
0176     Q_UNUSED( layer )
0177 
0178     bool const gpsActive = marbleModel()->positionTracking()->positionProviderPlugin() != nullptr;
0179     bool const positionAvailable = marbleModel()->positionTracking()->status() == PositionProviderStatusAvailable;
0180     bool const positionValid = m_currentPosition.isValid();
0181     if ( gpsActive && positionAvailable && positionValid ) {
0182         m_lastBoundingBox = viewport->viewLatLonAltBox();
0183 
0184         qreal screenPositionX, screenPositionY;
0185         if (!viewport->screenCoordinates( m_currentPosition, screenPositionX, screenPositionY )){
0186             return true;
0187         }
0188         const GeoDataCoordinates top( m_currentPosition.longitude(), m_currentPosition.latitude()+0.1 );
0189         qreal screenTopX, screenTopY;
0190         if (!viewport->screenCoordinates( top, screenTopX, screenTopY )){
0191             return true;
0192         }
0193         qreal const correction = -90.0 + RAD2DEG * atan2( screenPositionY -screenTopY, screenPositionX - screenTopX );
0194         const qreal rotation = m_heading + correction;
0195 
0196         if ( m_useCustomCursor ) {
0197             QTransform transform;
0198             transform.rotate( rotation );
0199             bool const highQuality = painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality;
0200             Qt::TransformationMode const mode = highQuality ? Qt::SmoothTransformation : Qt::FastTransformation;
0201             m_customCursorTransformed = m_customCursor.transformed( transform, mode );
0202         } else {
0203             // Calculate the scaled arrow shape
0204             const QPointF baseX( m_cursorSize, 0.0 );
0205             const QPointF baseY( 0.0, m_cursorSize );
0206             const QPointF relativeLeft  = - ( baseX * 9 ) + ( baseY * 9 );
0207             const QPointF relativeRight =   ( baseX * 9 ) + ( baseY * 9 );
0208             const QPointF relativeTip   = - ( baseY * 19.0 );
0209             m_arrow = QPolygonF() << QPointF( 0.0, 0.0 ) << relativeLeft << relativeTip << relativeRight;
0210 
0211             // Rotate the shape according to the current direction and move it to the screen center
0212             QMatrix transformation;
0213             transformation.translate( screenPositionX, screenPositionY );
0214             transformation.rotate( rotation );
0215             m_arrow = m_arrow * transformation;
0216 
0217             m_dirtyRegion = QRegion();
0218             m_dirtyRegion += ( m_arrow.boundingRect().toRect() );
0219             m_dirtyRegion += ( m_previousArrow.boundingRect().toRect() );
0220         }
0221 
0222         painter->save();
0223 
0224         GeoDataAccuracy accuracy = marbleModel()->positionTracking()->accuracy();
0225         if ( accuracy.horizontal > 0 && accuracy.horizontal < 1000 ) {
0226             // Paint a circle indicating the position accuracy
0227             painter->setPen( Qt::transparent );
0228             qreal planetRadius = m_marbleModel->planet()->radius();
0229             int width = qRound( accuracy.horizontal * viewport->radius() / planetRadius );
0230             if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) {
0231                 int arrowSize = qMax<int>( m_arrow.boundingRect().width(), m_arrow.boundingRect().height() );
0232                 width = qMax<int>( width, arrowSize + 10 );
0233             }
0234 
0235             painter->setBrush( m_accuracyColor );
0236             painter->drawEllipse( m_currentPosition, width, width );
0237         }
0238 
0239         // Draw trail if requested.
0240         if( m_showTrail ) {
0241             painter->save();
0242 
0243             // Use selected color to draw trail.
0244             painter->setBrush( m_trailColor );
0245             painter->setPen( m_trailColor );
0246 
0247             // we don't draw m_trail[0] which is current position
0248             for( int i = 1; i < m_trail.size(); ++i ) {
0249                 // Get screen coordinates from coordinates on the map.
0250                 qreal trailPointX, trailPointY;
0251                 viewport->screenCoordinates( m_trail[i], trailPointX, trailPointY );
0252 
0253                 const int size = ( sm_numTrailPoints - i ) * 3;
0254                 QRectF trailRect;
0255                 trailRect.setX( trailPointX - size / 2.0 );
0256                 trailRect.setY( trailPointY - size / 2.0 );
0257                 trailRect.setWidth( size );
0258                 trailRect.setHeight( size );
0259 
0260                 const qreal opacity = 1.0 - 0.15 * ( i - 1 );
0261                 painter->setOpacity( opacity );
0262                 painter->drawEllipse( trailRect );
0263             }
0264 
0265             painter->restore();
0266         }
0267 
0268         if( m_useCustomCursor)
0269         {
0270             painter->drawPixmap( m_currentPosition, m_customCursorTransformed );
0271         }
0272         else
0273         {
0274             painter->setPen( Qt::black );
0275             painter->setBrush( Qt::white );
0276             painter->drawPolygon( m_arrow );
0277         }
0278 
0279         painter->restore();
0280         m_previousArrow = m_arrow;
0281     }
0282     return true;
0283 }
0284 
0285 QHash<QString,QVariant> PositionMarker::settings() const
0286 {
0287     QHash<QString, QVariant> settings = RenderPlugin::settings();
0288 
0289     settings.insert(QStringLiteral("useCustomCursor"), m_useCustomCursor);
0290     settings.insert(QStringLiteral("cursorPath"), m_cursorPath);
0291     settings.insert(QStringLiteral("cursorSize"), m_cursorSize);
0292     settings.insert(QStringLiteral("acColor"), m_accuracyColor);
0293     settings.insert(QStringLiteral("trailColor"), m_trailColor);
0294     settings.insert(QStringLiteral("showTrail"), m_showTrail);
0295 
0296     return settings;
0297 }
0298 
0299 void PositionMarker::setSettings( const QHash<QString, QVariant> &settings )
0300 {
0301     RenderPlugin::setSettings( settings );
0302 
0303     const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0304     QColor defaultColor = Oxygen::brickRed4;
0305     defaultColor.setAlpha( smallScreen ? 80 : 40 );
0306 
0307     m_useCustomCursor = settings.value(QStringLiteral("useCustomCursor"), false).toBool();
0308     m_cursorPath = settings.value(QStringLiteral("cursorPath"), m_defaultCursorPath).toString();
0309     m_cursorSize = settings.value(QStringLiteral("cursorSize"), 1.0).toFloat();
0310     loadCustomCursor( m_cursorPath, m_useCustomCursor );
0311 
0312     m_accuracyColor = settings.value(QStringLiteral("acColor"), defaultColor).value<QColor>();
0313     m_trailColor = settings.value(QStringLiteral("trailColor"), QColor(0, 0, 255)).value<QColor>();
0314     m_showTrail = settings.value(QStringLiteral("showTrail"), false).toBool();
0315 
0316     readSettings();
0317 }
0318 
0319 void PositionMarker::readSettings()
0320 {
0321     if ( !m_configDialog ) {
0322         return;
0323     }
0324 
0325     if( m_useCustomCursor )
0326         ui_configWidget->m_customCursor->click();
0327     else
0328         ui_configWidget->m_originalCursor->click();
0329 
0330     bool found = false;
0331     float cursorSize = m_cursorSize;
0332     for( int i = 0; i < sm_numResizeSteps && !found; i++ )
0333     {
0334         if( sm_resizeSteps[i] == cursorSize )
0335         {
0336             ui_configWidget->m_resizeSlider->setValue( i );
0337             found = true;
0338         }
0339     }
0340     if( !found )
0341     {
0342         ui_configWidget->m_resizeSlider->setValue( sm_defaultSizeStep );
0343         cursorSize = sm_resizeSteps[sm_defaultSizeStep];
0344     }
0345 
0346     ui_configWidget->m_sizeLabel->setText( tr( "Cursor Size: %1" ).arg( cursorSize ) );
0347     QPalette palette = ui_configWidget->m_acColorChooserButton->palette();
0348     palette.setColor( QPalette::Button, m_accuracyColor );
0349     ui_configWidget->m_acColorChooserButton->setPalette( palette );
0350     palette = ui_configWidget->m_trailColorChooserButton->palette();
0351     palette.setColor( QPalette::Button, m_trailColor );
0352     ui_configWidget->m_trailColorChooserButton->setPalette( palette );
0353     ui_configWidget->m_trailCheckBox->setChecked( m_showTrail );
0354 }
0355 
0356 void PositionMarker::writeSettings()
0357 {
0358     if ( !m_configDialog ) {
0359         return;
0360     }
0361 
0362     m_useCustomCursor = ui_configWidget->m_customCursor->isChecked();
0363     m_cursorPath = m_cursorPath;
0364     m_cursorSize = sm_resizeSteps[ui_configWidget->m_resizeSlider->value()];
0365     m_accuracyColor = m_accuracyColor;
0366     m_trailColor = m_trailColor;
0367     m_showTrail = ui_configWidget->m_trailCheckBox->isChecked();
0368 
0369     emit settingsChanged( nameId() );
0370 }
0371 
0372 void PositionMarker::setPosition( const GeoDataCoordinates &position )
0373 {
0374     m_previousPosition = m_currentPosition;
0375     m_currentPosition = position;
0376     m_heading = marbleModel()->positionTracking()->direction();
0377     // Update the trail
0378     m_trail.push_front( m_currentPosition );
0379     for( int i = sm_numTrailPoints + 1; i< m_trail.size(); ++i ) {
0380             m_trail.pop_back();
0381     }
0382     if ( m_lastBoundingBox.contains( m_currentPosition ) )
0383     {
0384         emit repaintNeeded( m_dirtyRegion );
0385     }
0386 }
0387 
0388 void PositionMarker::chooseCustomCursor()
0389 {
0390     QString filename = QFileDialog::getOpenFileName( nullptr, tr( "Choose Custom Cursor" ) );
0391     if( !filename.isEmpty() )
0392         loadCustomCursor( filename, true );
0393 }
0394 
0395 void PositionMarker::loadCustomCursor( const QString& filename, bool useCursor )
0396 {
0397     m_customCursor = QPixmap( filename ).scaled( 22 * m_cursorSize, 22 * m_cursorSize, Qt::KeepAspectRatio, Qt::SmoothTransformation );
0398     if( !m_customCursor.isNull() )
0399     {
0400         if( m_configDialog )
0401         {
0402             if( useCursor )
0403                 ui_configWidget->m_customCursor->click();
0404             ui_configWidget->m_fileChooserButton->setIconSize( QSize( m_customCursor.width(), m_customCursor.height() ) );
0405             ui_configWidget->m_fileChooserButton->setIcon( QIcon( m_customCursor ) );
0406         }
0407         m_cursorPath = filename;
0408     }
0409     else
0410     {
0411         mDebug() << "Unable to load custom cursor from " << filename << ". "
0412                  << "The default cursor will be used instead";
0413         if ( m_configDialog )
0414             ui_configWidget->m_fileChooserButton->setIcon( QIcon( m_defaultCursor ) );
0415         m_customCursor = m_defaultCursor;
0416         m_cursorPath = m_defaultCursorPath;
0417     }
0418 }
0419 
0420 void PositionMarker::loadDefaultCursor()
0421 {
0422     m_defaultCursor = QPixmap( m_defaultCursorPath ).scaled( 22 * m_cursorSize, 22 * m_cursorSize, Qt::KeepAspectRatio, Qt::SmoothTransformation );
0423 }
0424 
0425 void PositionMarker::chooseColor()
0426 {
0427     QColor initialColor;
0428     if( sender() == ui_configWidget->m_acColorChooserButton ) {
0429         initialColor = m_accuracyColor;
0430     }
0431     else if( sender() == ui_configWidget->m_trailColorChooserButton ) {
0432         initialColor = m_trailColor;
0433     }
0434     QColor color = QColorDialog::getColor( initialColor, nullptr, 
0435                                            tr( "Please choose a color" ), 
0436                                            QColorDialog::ShowAlphaChannel );
0437     if( color.isValid() )
0438     {
0439         QPalette palette;
0440         if( sender() == ui_configWidget->m_acColorChooserButton ) {
0441             m_accuracyColor = color;
0442             palette = ui_configWidget->m_acColorChooserButton->palette();
0443             palette.setColor( QPalette::Button, m_accuracyColor );
0444             ui_configWidget->m_acColorChooserButton->setPalette( palette );
0445         }
0446         else if( sender() == ui_configWidget->m_trailColorChooserButton ) {
0447             m_trailColor = color;
0448             palette = ui_configWidget->m_trailColorChooserButton->palette();
0449             palette.setColor( QPalette::Button, m_trailColor );
0450             ui_configWidget->m_trailColorChooserButton->setPalette( palette );
0451         }
0452     }
0453 }
0454 
0455 void PositionMarker::resizeCursor( int step )
0456 {
0457     m_cursorSize = sm_resizeSteps[step];
0458     float newSize = 22 * m_cursorSize;
0459     m_customCursor = QPixmap( m_cursorPath ).scaled( newSize, newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation );
0460     ui_configWidget->m_sizeLabel->setText( tr( "Cursor Size: %1" ).arg( m_cursorSize ) );
0461     if( !m_customCursor.isNull() )
0462     {
0463         ui_configWidget->m_fileChooserButton->setIconSize( QSize( m_customCursor.width(), m_customCursor.height() ) );
0464         ui_configWidget->m_fileChooserButton->setIcon( QIcon( m_customCursor ) );
0465     }
0466     loadDefaultCursor();
0467 }
0468 
0469 qreal PositionMarker::zValue() const
0470 {
0471     return 1.0;
0472 }
0473 
0474 }
0475 
0476 #include "moc_PositionMarker.cpp"