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"