File indexing completed on 2024-04-28 03:50:18

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