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"