1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2008 Torsten Rahn <tackat@kde.org>
4 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
5 //
6
7 #include "MapScaleFloatItem.h"
8
9 #include <QContextMenuEvent>
10 #include <QDebug>
11 #include <QHelpEvent>
12 #include <QRect>
13 #include <QPainter>
14 #include <QPushButton>
15 #include <QMenu>
16 #include <QToolTip>
17
18 #include "ui_MapScaleConfigWidget.h"
19 #include "MarbleColors.h"
20 #include "MarbleDebug.h"
21 #include "MarbleGlobal.h"
22 #include "projections/AbstractProjection.h"
23 #include "MarbleLocale.h"
24 #include "MarbleModel.h"
25 #include "ViewportParams.h"
26 #include "GeoDataLatLonAltBox.h"
27
28 namespace Marble
29 {
30
MapScaleFloatItem(const MarbleModel * marbleModel)31 MapScaleFloatItem::MapScaleFloatItem( const MarbleModel *marbleModel )
32 : AbstractFloatItem( marbleModel, QPointF( 10.5, -10.5 ), QSizeF( 0.0, 40.0 ) ),
33 m_configDialog(nullptr),
34 m_radius(0),
35 m_target(QString()),
36 m_leftBarMargin(0),
37 m_rightBarMargin(0),
38 m_scaleBarWidth(0),
39 m_viewportWidth(0),
40 m_scaleBarHeight(5),
41 m_scaleBarDistance(0.0),
42 m_bestDivisor(0),
43 m_pixelInterval(0),
44 m_valueInterval(0),
45 m_scaleInitDone( false ),
46 m_showRatioScale( false ),
47 m_contextMenu( nullptr ),
48 m_minimized(false),
49 m_widthScaleFactor(2)
50 {
51 m_minimizeAction = new QAction(tr("Minimize"), this);
52 m_minimizeAction->setCheckable(true);
53 m_minimizeAction->setChecked(m_minimized);
54 connect(m_minimizeAction, SIGNAL(triggered()), this, SLOT(toggleMinimized()));
55 }
56
~MapScaleFloatItem()57 MapScaleFloatItem::~MapScaleFloatItem()
58 {
59 }
60
backendTypes() const61 QStringList MapScaleFloatItem::backendTypes() const
62 {
63 return QStringList(QStringLiteral("mapscale"));
64 }
65
name() const66 QString MapScaleFloatItem::name() const
67 {
68 return tr("Scale Bar");
69 }
70
guiString() const71 QString MapScaleFloatItem::guiString() const
72 {
73 return tr("&Scale Bar");
74 }
75
nameId() const76 QString MapScaleFloatItem::nameId() const
77 {
78 return QStringLiteral("scalebar");
79 }
80
version() const81 QString MapScaleFloatItem::version() const
82 {
83 return QStringLiteral("1.1");
84 }
85
description() const86 QString MapScaleFloatItem::description() const
87 {
88 return tr("This is a float item that provides a map scale.");
89 }
90
copyrightYears() const91 QString MapScaleFloatItem::copyrightYears() const
92 {
93 return QStringLiteral("2008, 2010, 2012");
94 }
95
pluginAuthors() const96 QVector<PluginAuthor> MapScaleFloatItem::pluginAuthors() const
97 {
98 return QVector<PluginAuthor>()
99 << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org"), tr("Original Developer"))
100 << PluginAuthor(QStringLiteral("Khanh-Nhan Nguyen"), QStringLiteral("khanh.nhan@wpi.edu"))
101 << PluginAuthor(QStringLiteral("Illya Kovalevskyy"), QStringLiteral("illya.kovalevskyy@gmail.com"));
102 }
103
icon() const104 QIcon MapScaleFloatItem::icon () const
105 {
106 return QIcon(QStringLiteral(":/icons/scalebar.png"));
107 }
108
109
initialize()110 void MapScaleFloatItem::initialize ()
111 {
112 }
113
isInitialized() const114 bool MapScaleFloatItem::isInitialized () const
115 {
116 return true;
117 }
118
setProjection(const ViewportParams * viewport)119 void MapScaleFloatItem::setProjection( const ViewportParams *viewport )
120 {
121 int viewportWidth = viewport->width();
122
123 QString target = marbleModel()->planetId();
124
125 if ( !( m_radius == viewport->radius()
126 && viewportWidth == m_viewportWidth
127 && m_target == target
128 && m_scaleInitDone ) )
129 {
130 int fontHeight = QFontMetrics( font() ).ascent();
131 if (m_showRatioScale) {
132 setContentSize( QSizeF( viewport->width() / m_widthScaleFactor,
133 fontHeight + 3 + m_scaleBarHeight + fontHeight + 7 ) );
134 } else {
135 setContentSize( QSizeF( viewport->width() / m_widthScaleFactor,
136 fontHeight + 3 + m_scaleBarHeight ) );
137 }
138
139 m_leftBarMargin = QFontMetrics( font() ).boundingRect( "0" ).width() / 2;
140 m_rightBarMargin = QFontMetrics( font() ).boundingRect( "0000" ).width() / 2;
141
142 m_scaleBarWidth = contentSize().width() - m_leftBarMargin - m_rightBarMargin;
143 m_viewportWidth = viewport->width();
144 m_radius = viewport->radius();
145 m_scaleInitDone = true;
146
147 m_pixel2Length = marbleModel()->planetRadius() /
148 (qreal)(viewport->radius());
149
150 if ( viewport->currentProjection()->surfaceType() == AbstractProjection::Cylindrical )
151 {
152 qreal centerLatitude = viewport->viewLatLonAltBox().center().latitude();
153 // For flat maps we calculate the length of the 90 deg section of the
154 // central latitude circle. For flat maps this distance matches
155 // the pixel based radius propertyy.
156 m_pixel2Length *= M_PI / 2 * cos( centerLatitude );
157 }
158
159 m_scaleBarDistance = (qreal)(m_scaleBarWidth) * m_pixel2Length;
160
161 const MarbleLocale::MeasurementSystem measurementSystem =
162 MarbleGlobal::getInstance()->locale()->measurementSystem();
163
164 if ( measurementSystem != MarbleLocale::MetricSystem ) {
165 m_scaleBarDistance *= KM2MI;
166 } else if (measurementSystem == MarbleLocale::NauticalSystem) {
167 m_scaleBarDistance *= KM2NM;
168 }
169
170 calcScaleBar();
171
172 update();
173 }
174
175 AbstractFloatItem::setProjection( viewport );
176 }
177
paintContent(QPainter * painter)178 void MapScaleFloatItem::paintContent( QPainter *painter )
179 {
180 painter->save();
181
182 painter->setRenderHint( QPainter::Antialiasing, true );
183
184 int fontHeight = QFontMetrics( font() ).ascent();
185
186 //calculate scale ratio
187 qreal displayMMPerPixel = 1.0 * painter->device()->widthMM() / painter->device()->width();
188 qreal ratio = m_pixel2Length / (displayMMPerPixel * MM2M);
189
190 //round ratio to 3 most significant digits, assume that ratio >= 1, otherwise it may display "1 : 0"
191 //i made this assumption because as the primary use case we do not need to zoom in that much
192 qreal power = 1;
193 int iRatio = (int)(ratio + 0.5); //round ratio to the nearest integer
194 while (iRatio >= 1000) {
195 iRatio /= 10;
196 power *= 10;
197 }
198 iRatio *= power;
199 m_ratioString.setNum(iRatio);
200 m_ratioString = QLatin1String("1 : ") + m_ratioString;
201
202 painter->setPen( QColor( Qt::darkGray ) );
203 painter->setBrush( QColor( Qt::darkGray ) );
204 painter->drawRect( m_leftBarMargin, fontHeight + 3,
205 m_scaleBarWidth,
206 m_scaleBarHeight );
207
208 painter->setPen( QColor( Qt::black ) );
209 painter->setBrush( QColor( Qt::white ) );
210 painter->drawRect( m_leftBarMargin, fontHeight + 3,
211 m_bestDivisor * m_pixelInterval, m_scaleBarHeight );
212
213 painter->setPen( QColor( Oxygen::aluminumGray4 ) );
214 painter->drawLine( m_leftBarMargin + 1, fontHeight + 2 + m_scaleBarHeight,
215 m_leftBarMargin + m_bestDivisor * m_pixelInterval - 1, fontHeight + 2 + m_scaleBarHeight );
216 painter->setPen( QColor( Qt::black ) );
217
218 painter->setBrush( QColor( Qt::black ) );
219
220 QString intervalStr;
221 int lastStringEnds = 0;
222 int currentStringBegin = 0;
223
224 for ( int j = 0; j <= m_bestDivisor; j += 2 ) {
225 if ( j < m_bestDivisor ) {
226 painter->drawRect( m_leftBarMargin + j * m_pixelInterval,
227 fontHeight + 3, m_pixelInterval - 1,
228 m_scaleBarHeight );
229
230 painter->setPen( QColor( Oxygen::aluminumGray5 ) );
231 painter->drawLine( m_leftBarMargin + j * m_pixelInterval + 1, fontHeight + 4,
232 m_leftBarMargin + (j + 1) * m_pixelInterval - 1, fontHeight + 4 );
233 painter->setPen( QColor( Qt::black ) );
234 }
235
236 MarbleLocale::MeasurementSystem distanceUnit;
237 distanceUnit = MarbleGlobal::getInstance()->locale()->measurementSystem();
238
239 QString unit = tr("km");
240 switch ( distanceUnit ) {
241 case MarbleLocale::MetricSystem:
242 if ( m_bestDivisor * m_valueInterval > 10000 ) {
243 unit = tr("km");
244 intervalStr.setNum( j * m_valueInterval / 1000 );
245 }
246 else {
247 unit = tr("m");
248 intervalStr.setNum( j * m_valueInterval );
249 }
250 break;
251 case MarbleLocale::ImperialSystem:
252 unit = tr("mi");
253 intervalStr.setNum( j * m_valueInterval / 1000 );
254
255 if ( m_bestDivisor * m_valueInterval > 3800 ) {
256 intervalStr.setNum( j * m_valueInterval / 1000 );
257 }
258 else {
259 intervalStr.setNum( qreal(j * m_valueInterval ) / 1000.0, 'f', 2 );
260 }
261 break;
262 case MarbleLocale::NauticalSystem:
263 unit = tr("nm");
264 intervalStr.setNum( j * m_valueInterval / 1000 );
265
266 if ( m_bestDivisor * m_valueInterval > 3800 ) {
267 intervalStr.setNum( j * m_valueInterval / 1000 );
268 }
269 else {
270 intervalStr.setNum( qreal(j * m_valueInterval ) / 1000.0, 'f', 2 );
271 }
272 break;
273 }
274
275 painter->setFont( font() );
276
277 if ( j == 0 ) {
278 const QString text = QLatin1String("0 ") + unit;
279 painter->drawText(0, fontHeight, text);
280 lastStringEnds = QFontMetrics(font()).width(text);
281 continue;
282 }
283
284 if( j == m_bestDivisor ) {
285 currentStringBegin = ( j * m_pixelInterval
286 - QFontMetrics( font() ).boundingRect( intervalStr ).width() );
287 }
288 else {
289 currentStringBegin = ( j * m_pixelInterval
290 - QFontMetrics( font() ).width( intervalStr ) / 2 );
291 }
292
293 if ( lastStringEnds < currentStringBegin ) {
294 painter->drawText( currentStringBegin, fontHeight, intervalStr );
295 lastStringEnds = currentStringBegin + QFontMetrics( font() ).width( intervalStr );
296 }
297 }
298
299 int leftRatioIndent = m_leftBarMargin + (m_scaleBarWidth - QFontMetrics( font() ).width(m_ratioString) ) / 2;
300 painter->drawText( leftRatioIndent, fontHeight + 3 + m_scaleBarHeight + fontHeight + 5, m_ratioString );
301
302 painter->restore();
303 }
304
calcScaleBar()305 void MapScaleFloatItem::calcScaleBar()
306 {
307 qreal magnitude = 1;
308
309 // First we calculate the exact length of the whole area that is possibly
310 // available to the scalebar in kilometers
311 int magValue = (int)( m_scaleBarDistance );
312
313 // We calculate the two most significant digits of the km-scalebar-length
314 // and store them in magValue.
315 while ( magValue >= 100 ) {
316 magValue /= 10;
317 magnitude *= 10;
318 }
319
320 m_bestDivisor = 4;
321 int bestMagValue = 1;
322
323 for ( int i = 0; i < magValue; i++ ) {
324 // We try to find the lowest divisor between 4 and 8 that
325 // divides magValue without remainder.
326 for ( int j = 4; j < 9; j++ ) {
327 if ( ( magValue - i ) % j == 0 ) {
328 // We store the very first result we find and store
329 // m_bestDivisor and bestMagValue as a final result.
330 m_bestDivisor = j;
331 bestMagValue = magValue - i;
332
333 // Stop all for loops and end search
334 i = magValue;
335 j = 9;
336 }
337 }
338
339 // If magValue doesn't divide through values between 4 and 8
340 // (e.g. because it's a prime number) try again with magValue
341 // decreased by i.
342 }
343
344 m_pixelInterval = (int)( m_scaleBarWidth * (qreal)( bestMagValue )
345 / (qreal)( magValue ) / m_bestDivisor );
346 m_valueInterval = (int)( bestMagValue * magnitude / m_bestDivisor );
347 }
348
configDialog()349 QDialog *MapScaleFloatItem::configDialog()
350 {
351 if ( !m_configDialog ) {
352 // Initializing configuration dialog
353 m_configDialog = new QDialog();
354 ui_configWidget = new Ui::MapScaleConfigWidget;
355 ui_configWidget->setupUi( m_configDialog );
356
357 readSettings();
358
359 connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()),
360 SLOT(writeSettings()) );
361 connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()),
362 SLOT(readSettings()) );
363
364 QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply );
365 connect( applyButton, SIGNAL(clicked()) ,
366 this, SLOT(writeSettings()) );
367 }
368 return m_configDialog;
369 }
370
contextMenuEvent(QWidget * w,QContextMenuEvent * e)371 void MapScaleFloatItem::contextMenuEvent( QWidget *w, QContextMenuEvent *e )
372 {
373 if ( !m_contextMenu ) {
374 m_contextMenu = contextMenu();
375
376 for( QAction *action: m_contextMenu->actions() ) {
377 if ( action->text() == tr( "&Configure..." ) ) {
378 m_contextMenu->removeAction( action );
379 break;
380 }
381 }
382
383 QAction *toggleAction = m_contextMenu->addAction( tr("&Ratio Scale"), this,
384 SLOT(toggleRatioScaleVisibility()) );
385 toggleAction->setCheckable( true );
386 toggleAction->setChecked( m_showRatioScale );
387
388 m_contextMenu->addAction(m_minimizeAction);
389 }
390
391 Q_ASSERT( m_contextMenu );
392 m_contextMenu->exec( w->mapToGlobal( e->pos() ) );
393 }
394
toolTipEvent(QHelpEvent * e)395 void MapScaleFloatItem::toolTipEvent( QHelpEvent *e )
396 {
397 QToolTip::showText( e->globalPos(), m_ratioString );
398 }
399
readSettings()400 void MapScaleFloatItem::readSettings()
401 {
402 if ( !m_configDialog )
403 return;
404
405 if ( m_showRatioScale ) {
406 ui_configWidget->m_showRatioScaleCheckBox->setCheckState( Qt::Checked );
407 } else {
408 ui_configWidget->m_showRatioScaleCheckBox->setCheckState( Qt::Unchecked );
409 }
410
411 ui_configWidget->m_minimizeCheckBox->setChecked(m_minimized);
412 }
413
writeSettings()414 void MapScaleFloatItem::writeSettings()
415 {
416 if ( ui_configWidget->m_showRatioScaleCheckBox->checkState() == Qt::Checked ) {
417 m_showRatioScale = true;
418 } else {
419 m_showRatioScale = false;
420 }
421
422 if (m_minimized != ui_configWidget->m_minimizeCheckBox->isChecked()) {
423 toggleMinimized();
424 }
425
426 emit settingsChanged( nameId() );
427 }
428
toggleRatioScaleVisibility()429 void MapScaleFloatItem::toggleRatioScaleVisibility()
430 {
431 m_showRatioScale = !m_showRatioScale;
432 readSettings();
433 emit settingsChanged( nameId() );
434 }
435
toggleMinimized()436 void MapScaleFloatItem::toggleMinimized()
437 {
438 m_minimized = !m_minimized;
439 ui_configWidget->m_minimizeCheckBox->setChecked(m_minimized);
440 m_minimizeAction->setChecked(m_minimized);
441 readSettings();
442 emit settingsChanged( nameId() );
443
444 if (m_minimized == true) {
445 m_widthScaleFactor = 4;
446 } else {
447 m_widthScaleFactor = 2;
448 }
449 }
450
451 }
452
453 #include "moc_MapScaleFloatItem.cpp"
454