File indexing completed on 2025-01-05 03:59:25

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2008 Torsten Rahn <tackat@kde.org>
0004 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
0005 //
0006 
0007 #include "MapScaleFloatItem.h"
0008 
0009 #include <QContextMenuEvent>
0010 #include <QDebug>
0011 #include <QHelpEvent>
0012 #include <QRect>
0013 #include <QPainter>
0014 #include <QPushButton>
0015 #include <QMenu>
0016 #include <QToolTip>
0017 
0018 #include <klocalizedstring.h>
0019 
0020 #include "ui_MapScaleConfigWidget.h"
0021 #include "MarbleColors.h"
0022 #include "MarbleGlobal.h"
0023 #include "AbstractProjection.h"
0024 #include "MarbleLocale.h"
0025 #include "MarbleModel.h"
0026 #include "ViewportParams.h"
0027 #include "GeoDataLatLonAltBox.h"
0028 
0029 namespace Marble
0030 {
0031 
0032 MapScaleFloatItem::MapScaleFloatItem( const MarbleModel *marbleModel )
0033     : AbstractFloatItem( marbleModel, QPointF( 10.5, -10.5 ), QSizeF( 0.0, 40.0 ) ),
0034       m_configDialog(nullptr),
0035       m_radius(0),
0036       m_target(QString()),
0037       m_leftBarMargin(0),
0038       m_rightBarMargin(0),
0039       m_scaleBarWidth(0),
0040       m_viewportWidth(0),
0041       m_scaleBarHeight(5),
0042       m_scaleBarDistance(0.0),
0043       m_bestDivisor(0),
0044       m_pixelInterval(0),
0045       m_valueInterval(0),
0046       m_scaleInitDone( false ),
0047       m_showRatioScale( false ),
0048       m_contextMenu( nullptr ),
0049       m_minimized(false),
0050       m_widthScaleFactor(2)
0051 {
0052     m_minimizeAction = new QAction(i18n("Minimize"), this);
0053     m_minimizeAction->setCheckable(true);
0054     m_minimizeAction->setChecked(m_minimized);
0055     connect(m_minimizeAction, SIGNAL(triggered()), this, SLOT(toggleMinimized()));
0056 }
0057 
0058 MapScaleFloatItem::~MapScaleFloatItem()
0059 {
0060 }
0061 
0062 QStringList MapScaleFloatItem::backendTypes() const
0063 {
0064     return QStringList(QStringLiteral("mapscale"));
0065 }
0066 
0067 QString MapScaleFloatItem::name() const
0068 {
0069     return i18n("Scale Bar");
0070 }
0071 
0072 QString MapScaleFloatItem::guiString() const
0073 {
0074     return i18n("&Scale Bar");
0075 }
0076 
0077 QString MapScaleFloatItem::nameId() const
0078 {
0079     return QStringLiteral("scalebar");
0080 }
0081 
0082 QString MapScaleFloatItem::version() const
0083 {
0084     return QStringLiteral("1.1");
0085 }
0086 
0087 QString MapScaleFloatItem::description() const
0088 {
0089     return i18n("A plugin to show a float item that provides a map scale.");
0090 }
0091 
0092 QString MapScaleFloatItem::copyrightYears() const
0093 {
0094     return QStringLiteral("2008, 2010, 2012");
0095 }
0096 
0097 QVector<PluginAuthor> MapScaleFloatItem::pluginAuthors() const
0098 {
0099     return QVector<PluginAuthor>()
0100             << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org"), i18n("Original Developer"))
0101             << PluginAuthor(QStringLiteral("Khanh-Nhan Nguyen"), QStringLiteral("khanh.nhan@wpi.edu"))
0102             << PluginAuthor(QStringLiteral("Illya Kovalevskyy"), QStringLiteral("illya.kovalevskyy@gmail.com"));
0103 }
0104 
0105 QIcon MapScaleFloatItem::icon () const
0106 {
0107     return QIcon::fromTheme(QStringLiteral("transform-scale-vertical"));
0108 }
0109 
0110 
0111 void MapScaleFloatItem::initialize ()
0112 {
0113 }
0114 
0115 bool MapScaleFloatItem::isInitialized () const
0116 {
0117     return true;
0118 }
0119 
0120 void MapScaleFloatItem::setProjection( const ViewportParams *viewport )
0121 {
0122     int viewportWidth = viewport->width();
0123 
0124     QString target = marbleModel()->planetId();
0125 
0126     if ( !(    m_radius == viewport->radius()
0127             && viewportWidth == m_viewportWidth
0128             && m_target == target
0129             && m_scaleInitDone ) )
0130     {
0131         int fontHeight     = QFontMetrics( font() ).ascent();
0132         if (m_showRatioScale) {
0133             setContentSize( QSizeF( viewport->width() / m_widthScaleFactor,
0134                                     fontHeight + 3 + m_scaleBarHeight + fontHeight + 7 ) );
0135         } else {
0136             setContentSize( QSizeF( viewport->width() / m_widthScaleFactor,
0137                                     fontHeight + 3 + m_scaleBarHeight ) );
0138         }
0139 
0140         m_leftBarMargin  = QFontMetrics( font() ).boundingRect( QLatin1String("0") ).width() / 2;
0141         m_rightBarMargin = QFontMetrics( font() ).boundingRect( QLatin1String("0000") ).width() / 2;
0142 
0143         m_scaleBarWidth = contentSize().width() - m_leftBarMargin - m_rightBarMargin;
0144         m_viewportWidth = viewport->width();
0145         m_radius = viewport->radius();
0146         m_scaleInitDone = true;
0147 
0148         m_pixel2Length = marbleModel()->planetRadius() /
0149                              (qreal)(viewport->radius());
0150 
0151         if ( viewport->currentProjection()->surfaceType() == AbstractProjection::Cylindrical )
0152         {
0153             qreal centerLatitude = viewport->viewLatLonAltBox().center().latitude();
0154             // For flat maps we calculate the length of the 90 deg section of the
0155             // central latitude circle. For flat maps this distance matches
0156             // the pixel based radius propertyy.
0157             m_pixel2Length *= M_PI / 2 * cos( centerLatitude );
0158         }
0159 
0160         m_scaleBarDistance = (qreal)(m_scaleBarWidth) * m_pixel2Length;
0161 
0162         const MarbleLocale::MeasurementSystem measurementSystem =
0163                 MarbleGlobal::getInstance()->locale()->measurementSystem();
0164 
0165         if ( measurementSystem != MarbleLocale::MetricSystem ) {
0166             m_scaleBarDistance *= KM2MI;
0167         } else if (measurementSystem == MarbleLocale::NauticalSystem) {
0168             m_scaleBarDistance *= KM2NM;
0169         }
0170 
0171         calcScaleBar();
0172 
0173         update();
0174     }
0175 
0176     AbstractFloatItem::setProjection( viewport );
0177 }
0178 
0179 void MapScaleFloatItem::paintContent( QPainter *painter )
0180 {
0181     painter->save();
0182 
0183     painter->setRenderHint( QPainter::Antialiasing, true );
0184 
0185     int fontHeight     = QFontMetrics( font() ).ascent();
0186 
0187     //calculate scale ratio
0188     qreal displayMMPerPixel = 1.0 * painter->device()->widthMM() / painter->device()->width();
0189     qreal ratio = m_pixel2Length / (displayMMPerPixel * MM2M);
0190 
0191     //round ratio to 3 most significant digits, assume that ratio >= 1, otherwise it may display "1 : 0"
0192     //i made this assumption because as the primary use case we do not need to zoom in that much
0193     qreal power = 1;
0194     int iRatio = (int)(ratio + 0.5); //round ratio to the nearest integer
0195     while (iRatio >= 1000) {
0196         iRatio /= 10;
0197         power *= 10;
0198     }
0199     iRatio *= power;
0200     m_ratioString.setNum(iRatio);
0201     m_ratioString = QLatin1String("1 : ") + m_ratioString;
0202 
0203     painter->setPen(   QColor( Qt::darkGray ) );
0204     painter->setBrush( QColor( Qt::darkGray ) );
0205     painter->drawRect( m_leftBarMargin, fontHeight + 3,
0206                        m_scaleBarWidth,
0207                        m_scaleBarHeight );
0208 
0209     painter->setPen(   QColor( Qt::black ) );
0210     painter->setBrush( QColor( Qt::white ) );
0211     painter->drawRect( m_leftBarMargin, fontHeight + 3,
0212                        m_bestDivisor * m_pixelInterval, m_scaleBarHeight );
0213 
0214     painter->setPen(   QColor( Oxygen::aluminumGray4 ) );
0215     painter->drawLine( m_leftBarMargin + 1, fontHeight + 2 + m_scaleBarHeight,
0216                        m_leftBarMargin + m_bestDivisor * m_pixelInterval - 1, fontHeight + 2 + m_scaleBarHeight );
0217     painter->setPen(   QColor( Qt::black ) );
0218 
0219     painter->setBrush( QColor( Qt::black ) );
0220 
0221     QString  intervalStr;
0222     int      lastStringEnds     = 0;
0223     int      currentStringBegin = 0;
0224 
0225     for ( int j = 0; j <= m_bestDivisor; j += 2 ) {
0226         if ( j < m_bestDivisor ) {
0227             painter->drawRect( m_leftBarMargin + j * m_pixelInterval,
0228                                fontHeight + 3, m_pixelInterval - 1,
0229                                m_scaleBarHeight );
0230 
0231         painter->setPen(   QColor( Oxygen::aluminumGray5 ) );
0232         painter->drawLine( m_leftBarMargin + j * m_pixelInterval + 1, fontHeight + 4,
0233                            m_leftBarMargin + (j + 1) * m_pixelInterval - 1, fontHeight + 4 );
0234         painter->setPen(   QColor( Qt::black ) );
0235         }
0236 
0237         MarbleLocale::MeasurementSystem distanceUnit;
0238         distanceUnit = MarbleGlobal::getInstance()->locale()->measurementSystem();
0239 
0240         QString unit = i18n("km");
0241         switch ( distanceUnit ) {
0242         case MarbleLocale::MetricSystem:
0243             if ( m_bestDivisor * m_valueInterval > 10000 ) {
0244                 unit = i18n("km");
0245                 intervalStr.setNum( j * m_valueInterval / 1000 );
0246             }
0247             else {
0248                 unit = i18n("m");
0249                 intervalStr.setNum( j * m_valueInterval );
0250             }
0251             break;
0252         case MarbleLocale::ImperialSystem:
0253             unit = i18n("mi");
0254             intervalStr.setNum( j * m_valueInterval / 1000 );
0255 
0256             if ( m_bestDivisor * m_valueInterval > 3800 ) {
0257                 intervalStr.setNum( j * m_valueInterval / 1000 );
0258             }
0259             else {
0260                 intervalStr.setNum( qreal(j * m_valueInterval ) / 1000.0, 'f', 2 );
0261             }
0262             break;
0263         case MarbleLocale::NauticalSystem:
0264             unit = i18n("nm");
0265             intervalStr.setNum( j * m_valueInterval / 1000 );
0266 
0267             if ( m_bestDivisor * m_valueInterval > 3800 ) {
0268                 intervalStr.setNum( j * m_valueInterval / 1000 );
0269             }
0270             else {
0271                 intervalStr.setNum( qreal(j * m_valueInterval ) / 1000.0, 'f', 2 );
0272             }
0273             break;
0274         }
0275 
0276         painter->setFont( font() );
0277 
0278         if ( j == 0 ) {
0279             const QString text = QLatin1String("0 ") + unit;
0280             painter->drawText(0, fontHeight, text);
0281             lastStringEnds = QFontMetrics(font()).horizontalAdvance(text);
0282             continue;
0283         }
0284 
0285         if( j == m_bestDivisor ) {
0286             currentStringBegin = ( j * m_pixelInterval
0287                                    - QFontMetrics( font() ).boundingRect( intervalStr ).width() );
0288         }
0289         else {
0290             currentStringBegin = ( j * m_pixelInterval
0291                                    - QFontMetrics( font() ).horizontalAdvance( intervalStr ) / 2 );
0292         }
0293 
0294         if ( lastStringEnds < currentStringBegin ) {
0295             painter->drawText( currentStringBegin, fontHeight, intervalStr );
0296             lastStringEnds = currentStringBegin + QFontMetrics( font() ).horizontalAdvance( intervalStr );
0297         }
0298     }
0299 
0300     int leftRatioIndent = m_leftBarMargin + (m_scaleBarWidth - QFontMetrics( font() ).horizontalAdvance(m_ratioString) ) / 2;
0301     painter->drawText( leftRatioIndent, fontHeight + 3 + m_scaleBarHeight + fontHeight + 5, m_ratioString );
0302 
0303     painter->restore();
0304 }
0305 
0306 void MapScaleFloatItem::calcScaleBar()
0307 {
0308     qreal  magnitude = 1;
0309 
0310     // First we calculate the exact length of the whole area that is possibly
0311     // available to the scalebar in kilometers
0312     int  magValue = (int)( m_scaleBarDistance );
0313 
0314     // We calculate the two most significant digits of the km-scalebar-length
0315     // and store them in magValue.
0316     while ( magValue >= 100 ) {
0317         magValue  /= 10;
0318         magnitude *= 10;
0319     }
0320 
0321     m_bestDivisor = 4;
0322     int  bestMagValue = 1;
0323 
0324     for ( int i = 0; i < magValue; i++ ) {
0325         // We try to find the lowest divisor between 4 and 8 that
0326         // divides magValue without remainder.
0327         for ( int j = 4; j < 9; j++ ) {
0328             if ( ( magValue - i ) % j == 0 ) {
0329                 // We store the very first result we find and store
0330                 // m_bestDivisor and bestMagValue as a final result.
0331                 m_bestDivisor = j;
0332                 bestMagValue  = magValue - i;
0333 
0334                 // Stop all for loops and end search
0335                 i = magValue;
0336                 j = 9;
0337             }
0338         }
0339 
0340         // If magValue doesn't divide through values between 4 and 8
0341         // (e.g. because it's a prime number) try again with magValue
0342         // decreased by i.
0343     }
0344 
0345     m_pixelInterval = (int)( m_scaleBarWidth * (qreal)( bestMagValue )
0346                              / (qreal)( magValue ) / m_bestDivisor );
0347     m_valueInterval = (int)( bestMagValue * magnitude / m_bestDivisor );
0348 }
0349 
0350 QDialog *MapScaleFloatItem::configDialog()
0351 {
0352     if ( !m_configDialog ) {
0353         // Initializing configuration dialog
0354         m_configDialog = new QDialog();
0355         ui_configWidget = new Ui::MapScaleConfigWidget;
0356         ui_configWidget->setupUi( m_configDialog );
0357 
0358         readSettings();
0359 
0360         connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()),
0361                                             SLOT(writeSettings()) );
0362         connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()),
0363                                             SLOT(readSettings()) );
0364 
0365         QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply );
0366         connect( applyButton, SIGNAL(clicked()) ,
0367                 this,        SLOT(writeSettings()) );
0368     }
0369     return m_configDialog;
0370 }
0371 
0372 void MapScaleFloatItem::contextMenuEvent( QWidget *w, QContextMenuEvent *e )
0373 {
0374     if ( !m_contextMenu ) {
0375         m_contextMenu = contextMenu();
0376 
0377         for( QAction *action: m_contextMenu->actions() ) {
0378             if ( action->text() == i18n( "&Configure..." ) ) {
0379                 m_contextMenu->removeAction( action );
0380                 break;
0381             }
0382         }
0383 
0384         QAction *toggleAction = m_contextMenu->addAction( i18n("&Ratio Scale"), this,
0385                                                 SLOT(toggleRatioScaleVisibility()) );
0386         toggleAction->setCheckable( true );
0387         toggleAction->setChecked( m_showRatioScale );
0388 
0389         m_contextMenu->addAction(m_minimizeAction);
0390     }
0391 
0392     Q_ASSERT( m_contextMenu );
0393     m_contextMenu->exec( w->mapToGlobal( e->pos() ) );
0394 }
0395 
0396 void MapScaleFloatItem::toolTipEvent( QHelpEvent *e )
0397 {
0398     QToolTip::showText( e->globalPos(), m_ratioString );
0399 }
0400 
0401 void MapScaleFloatItem::readSettings()
0402 {
0403     if ( !m_configDialog )
0404         return;
0405 
0406     if ( m_showRatioScale ) {
0407         ui_configWidget->m_showRatioScaleCheckBox->setCheckState( Qt::Checked );
0408     } else {
0409         ui_configWidget->m_showRatioScaleCheckBox->setCheckState( Qt::Unchecked );
0410     }
0411 
0412     ui_configWidget->m_minimizeCheckBox->setChecked(m_minimized);
0413 }
0414 
0415 void MapScaleFloatItem::writeSettings()
0416 {
0417     if ( ui_configWidget->m_showRatioScaleCheckBox->checkState() == Qt::Checked ) {
0418         m_showRatioScale = true;
0419     } else {
0420         m_showRatioScale = false;
0421     }
0422 
0423     if (m_minimized != ui_configWidget->m_minimizeCheckBox->isChecked()) {
0424         toggleMinimized();
0425     }
0426 
0427     Q_EMIT settingsChanged( nameId() );
0428 }
0429 
0430 void MapScaleFloatItem::toggleRatioScaleVisibility()
0431 {
0432     m_showRatioScale = !m_showRatioScale;
0433     readSettings();
0434     Q_EMIT settingsChanged( nameId() );
0435 }
0436 
0437 void MapScaleFloatItem::toggleMinimized()
0438 {
0439     m_minimized = !m_minimized;
0440     ui_configWidget->m_minimizeCheckBox->setChecked(m_minimized);
0441     m_minimizeAction->setChecked(m_minimized);
0442     readSettings();
0443     Q_EMIT settingsChanged( nameId() );
0444 
0445     if (m_minimized == true) {
0446         m_widthScaleFactor = 4;
0447     } else {
0448         m_widthScaleFactor = 2;
0449     }
0450 }
0451 
0452 } // namespace Marble
0453 
0454 #include "moc_MapScaleFloatItem.cpp"