File indexing completed on 2024-04-28 11:30:58
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2008 Henry de Valence <hdevalence@gmail.com> 0004 // SPDX-FileCopyrightText: 2011 Friedrich W. H. Kossebau <kossebau@kde.org> 0005 0006 0007 #include "LatLonEdit.h" 0008 #include "ui_LatLonEdit.h" 0009 0010 #include "MarbleDebug.h" 0011 0012 0013 0014 namespace Marble 0015 { 0016 0017 // This widget can have 3 different designs, one per notation (Decimal, DMS, DM) 0018 // To reduce the footprint this was not implemented using a stack of widgets 0019 // where each widget offer the needed design for another notation. 0020 // Instead, as Decimal and DM are both using subsets of the UI elements used 0021 // for DMS, just the UI elements for DMS are created and modified as needed, 0022 // if another notation is selected. This involves showing and hiding them and 0023 // setting the proper suffix and min/max values. 0024 // The logic per notation is moved into specialized subclasses of a class 0025 // AbstractInputHandler. 0026 // TODO: simply remove the LatLonEdit.ui file and embed code directly here? 0027 0028 enum { PositiveSphereIndex = 0, NegativeSphereIndex = 1 }; 0029 0030 0031 class LatLonEditPrivate; 0032 0033 class AbstractInputHandler // TODO: better name 0034 { 0035 protected: 0036 explicit AbstractInputHandler(LatLonEditPrivate *ui) : m_ui(ui) {} 0037 public: 0038 virtual ~AbstractInputHandler() {} 0039 0040 public: // API to be implemented 0041 virtual void setupUi() = 0; 0042 virtual void setupMinMax(LatLonEdit::Dimension dimension) = 0; 0043 virtual void setValue(qreal value) = 0; 0044 virtual void handleIntEditChange() = 0; 0045 virtual void handleUIntEditChange() = 0; 0046 virtual void handleFloatEditChange() = 0; 0047 virtual qreal calculateValue() const = 0; 0048 0049 protected: 0050 LatLonEditPrivate * const m_ui; 0051 }; 0052 0053 class DecimalInputHandler : public AbstractInputHandler 0054 { 0055 public: 0056 explicit DecimalInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {} 0057 public: // AbstractInputHandler API 0058 void setupUi() override; 0059 void setupMinMax(LatLonEdit::Dimension dimension) override; 0060 void setValue(qreal value) override; 0061 void handleIntEditChange() override; 0062 void handleUIntEditChange() override; 0063 void handleFloatEditChange() override; 0064 qreal calculateValue() const override; 0065 }; 0066 0067 class DMSInputHandler : public AbstractInputHandler 0068 { 0069 public: 0070 explicit DMSInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {} 0071 public: // AbstractInputHandler API 0072 void setupUi() override; 0073 void setupMinMax(LatLonEdit::Dimension dimension) override; 0074 void setValue(qreal value) override; 0075 void handleIntEditChange() override; 0076 void handleUIntEditChange() override; 0077 void handleFloatEditChange() override; 0078 qreal calculateValue() const override; 0079 }; 0080 0081 class DMInputHandler : public AbstractInputHandler 0082 { 0083 public: 0084 explicit DMInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {} 0085 public: // AbstractInputHandler API 0086 void setupUi() override; 0087 void setupMinMax(LatLonEdit::Dimension dimension) override; 0088 void setValue(qreal value) override; 0089 void handleIntEditChange() override; 0090 void handleUIntEditChange() override; 0091 void handleFloatEditChange() override; 0092 0093 qreal calculateValue() const override; 0094 }; 0095 0096 class LatLonEditPrivate : public Ui::LatLonEditPrivate 0097 { 0098 friend class DecimalInputHandler; 0099 friend class DMSInputHandler; 0100 friend class DMInputHandler; 0101 0102 public: 0103 LatLonEdit::Dimension m_dimension; 0104 qreal m_value; 0105 GeoDataCoordinates::Notation m_notation; 0106 AbstractInputHandler *m_inputHandler; 0107 // flag which indicates that the widgets are updated due to a change 0108 // in one of the other widgets. Q*SpinBox does not have a signal which is 0109 // only emitted by a change due to user input, not code setting a new value. 0110 // This flag should be less expensive then disconnecting from and reconnecting 0111 // to the valueChanged signal of all widgets. 0112 bool m_updating : 1; 0113 0114 LatLonEditPrivate(); 0115 ~LatLonEditPrivate(); 0116 void init(QWidget* parent); 0117 }; 0118 0119 0120 static void 0121 switchSign( QComboBox *sign ) 0122 { 0123 const bool isNegativeSphere = (sign->currentIndex() == NegativeSphereIndex); 0124 sign->setCurrentIndex( isNegativeSphere ? PositiveSphereIndex : NegativeSphereIndex ); 0125 } 0126 0127 void DecimalInputHandler::setupUi() 0128 { 0129 m_ui->m_floatValueEditor->setSuffix(LatLonEdit::tr("\xC2\xB0")); // the degree symbol ° 0130 m_ui->m_floatValueEditor->setDecimals(5); 0131 0132 m_ui->m_intValueEditor->hide(); 0133 m_ui->m_uintValueEditor->hide(); 0134 } 0135 0136 void DecimalInputHandler::setupMinMax(LatLonEdit::Dimension dimension) 0137 { 0138 const qreal maxValue = (dimension == LatLonEdit::Longitude) ? 180.0 : 90.0; 0139 0140 m_ui->m_floatValueEditor->setMinimum(-maxValue); 0141 m_ui->m_floatValueEditor->setMaximum( maxValue); 0142 } 0143 0144 void DecimalInputHandler::setValue(qreal value) 0145 { 0146 value = qAbs(value); 0147 0148 m_ui->m_floatValueEditor->setValue(value); 0149 } 0150 0151 void DecimalInputHandler::handleIntEditChange() 0152 { 0153 // nothing to do, perhaps rather disconnect the signal with this notation 0154 } 0155 0156 void DecimalInputHandler::handleUIntEditChange() 0157 { 0158 // nothing to do, perhaps rather disconnect the signal with this notation 0159 } 0160 0161 void DecimalInputHandler::handleFloatEditChange() 0162 { 0163 // nothing to do, perhaps rather disconnect the signal with this notation 0164 } 0165 0166 qreal DecimalInputHandler::calculateValue() const 0167 { 0168 qreal value = m_ui->m_floatValueEditor->value(); 0169 0170 if (m_ui->m_sign->currentIndex() == NegativeSphereIndex) { 0171 value *= -1; 0172 } 0173 0174 return value; 0175 } 0176 0177 void DMSInputHandler::setupUi() 0178 { 0179 m_ui->m_uintValueEditor->setSuffix(LatLonEdit::tr("'")); 0180 m_ui->m_floatValueEditor->setSuffix(LatLonEdit::tr("\"")); 0181 m_ui->m_floatValueEditor->setDecimals(2); 0182 0183 m_ui->m_intValueEditor->show(); 0184 m_ui->m_uintValueEditor->show(); 0185 } 0186 0187 void DMSInputHandler::setupMinMax(LatLonEdit::Dimension dimension) 0188 { 0189 const int maxValue = (dimension == LatLonEdit::Longitude) ? 180 : 90; 0190 0191 m_ui->m_intValueEditor->setMinimum(-maxValue); 0192 m_ui->m_intValueEditor->setMaximum( maxValue); 0193 } 0194 0195 void DMSInputHandler::setValue(qreal value) 0196 { 0197 value = qAbs( value ); 0198 0199 int degValue = (int) value; 0200 0201 qreal minFValue = 60 * (value - degValue); 0202 int minValue = (int) minFValue; 0203 qreal secFValue = 60 * (minFValue - minValue); 0204 // Adjustment for fuzziness (like 49.999999999999999999999) 0205 int secValue = qRound(secFValue); 0206 if (secValue > 59) { 0207 secFValue = 0.0; 0208 ++minValue; 0209 } 0210 if (minValue > 59) { 0211 minValue = 0; 0212 ++degValue; 0213 } 0214 0215 m_ui->m_intValueEditor->setValue( degValue ); 0216 m_ui->m_uintValueEditor->setValue( minValue ); 0217 m_ui->m_floatValueEditor->setValue( secFValue ); 0218 } 0219 0220 void DMSInputHandler::handleIntEditChange() 0221 { 0222 const int degValue = m_ui->m_intValueEditor->value(); 0223 const int minDegValue = m_ui->m_intValueEditor->minimum(); 0224 const int maxDegValue = m_ui->m_intValueEditor->maximum(); 0225 // at max/min? 0226 if (degValue <= minDegValue || maxDegValue <= degValue) { 0227 m_ui->m_uintValueEditor->setValue( 0 ); 0228 m_ui->m_floatValueEditor->setValue( 0.0 ); 0229 } 0230 } 0231 0232 void DMSInputHandler::handleUIntEditChange() 0233 { 0234 const int degValue = m_ui->m_intValueEditor->value(); 0235 const int minValue = m_ui->m_uintValueEditor->value(); 0236 0237 if (minValue < 0) { 0238 if (degValue != 0) { 0239 m_ui->m_uintValueEditor->setValue( 59 ); 0240 const int degDec = (degValue > 0) ? 1 : -1; 0241 m_ui->m_intValueEditor->setValue( degValue - degDec ); 0242 } else { 0243 switchSign( m_ui->m_sign ); 0244 m_ui->m_uintValueEditor->setValue( 1 ); 0245 } 0246 } else { 0247 const int minDegValue = m_ui->m_intValueEditor->minimum(); 0248 const int maxDegValue = m_ui->m_intValueEditor->maximum(); 0249 // at max/min already? 0250 if (degValue <= minDegValue || maxDegValue <= degValue) { 0251 // ignore 0252 m_ui->m_uintValueEditor->setValue( 0 ); 0253 // overflow? 0254 } else if (minValue >= 60) { 0255 m_ui->m_uintValueEditor->setValue( 0 ); 0256 // will reach max/min? 0257 if (minDegValue+1 == degValue || degValue == maxDegValue-1) { 0258 // reset also sec 0259 m_ui->m_floatValueEditor->setValue( 0.0 ); 0260 } 0261 const int degInc = (degValue > 0) ? 1 : -1; 0262 m_ui->m_intValueEditor->setValue( degValue + degInc ); 0263 } 0264 } 0265 } 0266 0267 void DMSInputHandler::handleFloatEditChange() 0268 { 0269 const int degValue = m_ui->m_intValueEditor->value(); 0270 const int minValue = m_ui->m_uintValueEditor->value(); 0271 const qreal secValue = m_ui->m_floatValueEditor->value(); 0272 0273 if (secValue < 0.0) { 0274 const qreal secDiff = -secValue; 0275 if (degValue == 0 && minValue == 0) { 0276 switchSign( m_ui->m_sign ); 0277 m_ui->m_floatValueEditor->setValue( secDiff ); 0278 } else { 0279 m_ui->m_floatValueEditor->setValue( 60.0 - secDiff ); 0280 if (minValue > 0) { 0281 m_ui->m_uintValueEditor->setValue( minValue - 1 ); 0282 } else { 0283 m_ui->m_uintValueEditor->setValue( 59 ); 0284 const int degDec = (degValue > 0) ? 1 : -1; 0285 m_ui->m_intValueEditor->setValue( degValue - degDec ); 0286 } 0287 } 0288 } else { 0289 const int minDegValue = m_ui->m_intValueEditor->minimum(); 0290 const int maxDegValue = m_ui->m_intValueEditor->maximum(); 0291 // at max/min already? 0292 if (degValue <= minDegValue || maxDegValue <= degValue) { 0293 // ignore 0294 m_ui->m_floatValueEditor->setValue( 0.0 ); 0295 // need to inc minutes? 0296 } else if (secValue >= 60.0) { 0297 qreal newSec = secValue - 60.0; 0298 // will reach max/min? 0299 if (minValue == 59) { 0300 m_ui->m_uintValueEditor->setValue( 0 ); 0301 // will reach max/min? 0302 if (minDegValue+1 == degValue || degValue == maxDegValue-1) { 0303 // reset also sec 0304 newSec = 0.0; 0305 } 0306 const int degInc = (degValue > 0) ? 1 : -1; 0307 m_ui->m_intValueEditor->setValue( degValue + degInc ); 0308 } else { 0309 m_ui->m_uintValueEditor->setValue( minValue + 1 ); 0310 } 0311 m_ui->m_floatValueEditor->setValue( newSec ); 0312 } 0313 } 0314 } 0315 0316 qreal DMSInputHandler::calculateValue() const 0317 { 0318 const bool isNegativeDeg = ( m_ui->m_intValueEditor->value() < 0 ); 0319 0320 const qreal deg = (qreal)(qAbs(m_ui->m_intValueEditor->value())); 0321 const qreal min = (qreal)(m_ui->m_uintValueEditor->value()) / 60.0; 0322 const qreal sec = m_ui->m_floatValueEditor->value() / 3600.0; 0323 0324 qreal value = deg + min + sec; 0325 0326 if (isNegativeDeg) { 0327 value *= -1; 0328 } 0329 if (m_ui->m_sign->currentIndex() == NegativeSphereIndex) { 0330 value *= -1; 0331 } 0332 0333 return value; 0334 } 0335 0336 void DMInputHandler::setupUi() 0337 { 0338 m_ui->m_floatValueEditor->setSuffix(LatLonEdit::tr("'")); 0339 m_ui->m_floatValueEditor->setDecimals(2); 0340 0341 m_ui->m_intValueEditor->show(); 0342 m_ui->m_uintValueEditor->hide(); 0343 } 0344 0345 void DMInputHandler::setupMinMax(LatLonEdit::Dimension dimension) 0346 { 0347 const int maxValue = (dimension == LatLonEdit::Longitude) ? 180 : 90; 0348 0349 m_ui->m_intValueEditor->setMinimum(-maxValue); 0350 m_ui->m_intValueEditor->setMaximum( maxValue); 0351 } 0352 0353 void DMInputHandler::setValue(qreal value) 0354 { 0355 value = qAbs(value); 0356 0357 int degValue = (int)value; 0358 0359 qreal minFValue = 60 * (value - degValue); 0360 // Adjustment for fuzziness (like 49.999999999999999999999) 0361 int minValue = qRound( minFValue ); 0362 if (minValue > 59) { 0363 minFValue = 0.0; 0364 ++degValue; 0365 } 0366 0367 m_ui->m_intValueEditor->setValue( degValue ); 0368 m_ui->m_floatValueEditor->setValue( minFValue ); 0369 } 0370 0371 void DMInputHandler::handleIntEditChange() 0372 { 0373 const int degValue = m_ui->m_intValueEditor->value(); 0374 const int minDegValue = m_ui->m_intValueEditor->minimum(); 0375 const int maxDegValue = m_ui->m_intValueEditor->maximum(); 0376 // at max/min? 0377 if (degValue <= minDegValue || maxDegValue <= degValue) { 0378 m_ui->m_floatValueEditor->setValue( 0.0 ); 0379 } 0380 } 0381 0382 void DMInputHandler::handleUIntEditChange() 0383 { 0384 // nothing to be done here, should be never called 0385 } 0386 0387 void DMInputHandler::handleFloatEditChange() 0388 { 0389 const int degValue = m_ui->m_intValueEditor->value(); 0390 const qreal minValue = m_ui->m_floatValueEditor->value(); 0391 0392 if (minValue < 0.0) { 0393 const qreal minDiff = -minValue; 0394 if (degValue == 0) { 0395 switchSign( m_ui->m_sign ); 0396 m_ui->m_floatValueEditor->setValue( minDiff ); 0397 } else { 0398 m_ui->m_floatValueEditor->setValue( 60.0 - minDiff ); 0399 m_ui->m_intValueEditor->setValue( degValue - 1 ); 0400 } 0401 } else { 0402 const int minDegValue = m_ui->m_intValueEditor->minimum(); 0403 const int maxDegValue = m_ui->m_intValueEditor->maximum(); 0404 // at max/min already? 0405 if (degValue <= minDegValue || maxDegValue <= degValue) { 0406 // ignore 0407 m_ui->m_floatValueEditor->setValue( 0.0 ); 0408 // need to inc degrees? 0409 } else if (minValue >= 60.0) { 0410 qreal newMin = minValue - 60.0; 0411 // will reach max/min? 0412 if (minDegValue+1 == degValue || degValue == maxDegValue-1) { 0413 // reset also sec 0414 newMin = 0.0; 0415 } else { 0416 m_ui->m_intValueEditor->setValue( degValue + 1 ); 0417 } 0418 m_ui->m_floatValueEditor->setValue( newMin ); 0419 } 0420 } 0421 } 0422 0423 qreal DMInputHandler::calculateValue() const 0424 { 0425 const bool isNegativeDeg = ( m_ui->m_intValueEditor->value() < 0 ); 0426 0427 const qreal deg = (qreal)(qAbs(m_ui->m_intValueEditor->value())); 0428 const qreal min = m_ui->m_floatValueEditor->value() / 60.0; 0429 0430 qreal value = deg + min; 0431 0432 if (isNegativeDeg) { 0433 value *= -1; 0434 } 0435 if (m_ui->m_sign->currentIndex() == NegativeSphereIndex) { 0436 value *= -1; 0437 } 0438 0439 return value; 0440 } 0441 0442 0443 LatLonEditPrivate::LatLonEditPrivate() 0444 : m_dimension(LatLonEdit::Latitude) 0445 , m_value(0.0) 0446 , m_notation(GeoDataCoordinates::DMS) 0447 , m_inputHandler(new DMSInputHandler(this)) 0448 , m_updating(false) 0449 {} 0450 0451 LatLonEditPrivate::~LatLonEditPrivate() 0452 { 0453 delete m_inputHandler; 0454 } 0455 0456 void LatLonEditPrivate::init(QWidget* parent) { setupUi(parent); } 0457 0458 } 0459 0460 0461 using namespace Marble; 0462 0463 LatLonEdit::LatLonEdit(QWidget *parent, LatLonEdit::Dimension dimension, GeoDataCoordinates::Notation notation) 0464 : QWidget( parent ), d(new LatLonEditPrivate()) 0465 { 0466 d->init(this); 0467 setDimension(dimension); 0468 setNotation(notation); 0469 0470 connect(d->m_intValueEditor, SIGNAL(valueChanged(int)), this, SLOT(checkIntValueOverflow())); 0471 connect(d->m_uintValueEditor, SIGNAL(valueChanged(int)), this, SLOT(checkUIntValueOverflow())); 0472 connect(d->m_floatValueEditor, SIGNAL(valueChanged(double)), this, SLOT(checkFloatValueOverflow())); 0473 0474 connect(d->m_sign, SIGNAL(currentIndexChanged(int)), 0475 this, SLOT(onSignChanged())); 0476 } 0477 0478 LatLonEdit::~LatLonEdit() 0479 { 0480 delete d; 0481 } 0482 0483 qreal LatLonEdit::value() const 0484 { 0485 return d->m_value; 0486 } 0487 0488 GeoDataCoordinates::Notation LatLonEdit::notation() const 0489 { 0490 return d->m_notation; 0491 } 0492 0493 void LatLonEdit::onSignChanged() 0494 { 0495 if( d->m_updating ) 0496 return; 0497 0498 // Only flip the value if it does not match the sign 0499 if (d->m_sign->currentIndex() == PositiveSphereIndex) { 0500 if (d->m_value < 0.0) { 0501 d->m_value *= -1; 0502 } 0503 } else { 0504 if (d->m_value > 0.0) { 0505 d->m_value *= -1; 0506 } 0507 } 0508 0509 emit valueChanged( d->m_value ); 0510 } 0511 0512 void LatLonEdit::setDimension( Dimension dimension ) 0513 { 0514 d->m_dimension = dimension; 0515 0516 d->m_updating = true; 0517 0518 d->m_inputHandler->setupMinMax(dimension); 0519 0520 // update sign widget content 0521 { 0522 d->m_sign->clear(); 0523 0524 switch (dimension) { 0525 case Longitude: 0526 d->m_sign->addItem( tr("E", "East, the direction" ) ); 0527 d->m_sign->addItem( tr("W", "West, the direction" ) ); 0528 break; 0529 case Latitude: 0530 d->m_sign->addItem( tr("N", "North, the direction" ) ); 0531 d->m_sign->addItem( tr("S", "South, the direction" ) ); 0532 break; 0533 } 0534 } 0535 0536 d->m_updating = false; 0537 0538 // reset value, old one is useless 0539 setValue( 0.0 ); 0540 } 0541 0542 void LatLonEdit::setNotation(GeoDataCoordinates::Notation notation) 0543 { 0544 delete d->m_inputHandler; 0545 d->m_inputHandler = nullptr; 0546 0547 switch (notation) { 0548 case GeoDataCoordinates::Decimal: 0549 d->m_inputHandler = new DecimalInputHandler(d); 0550 break; 0551 case GeoDataCoordinates::DMS: 0552 d->m_inputHandler = new DMSInputHandler(d); 0553 break; 0554 case GeoDataCoordinates::DM: 0555 d->m_inputHandler = new DMInputHandler(d); 0556 break; 0557 case GeoDataCoordinates::UTM: 0558 /** @todo see below */ 0559 break; 0560 case GeoDataCoordinates::Astro: 0561 /** @todo see below */ 0562 break; 0563 } 0564 0565 if (!d->m_inputHandler) { 0566 /** @todo Temporary fallback to DecimalInputHandler 0567 * Implement proper handlers for UTM and Astro */ 0568 d->m_inputHandler = new DecimalInputHandler(d); 0569 } 0570 0571 d->m_notation = notation; 0572 d->m_inputHandler->setupUi(); 0573 d->m_inputHandler->setupMinMax(d->m_dimension); 0574 d->m_inputHandler->setValue(d->m_value); 0575 } 0576 0577 void LatLonEdit::checkFloatValueOverflow() 0578 { 0579 if( d->m_updating ) 0580 return; 0581 0582 d->m_updating = true; 0583 0584 d->m_inputHandler->handleFloatEditChange(); 0585 0586 d->m_updating = false; 0587 0588 recalculate(); 0589 } 0590 0591 0592 void LatLonEdit::checkUIntValueOverflow() 0593 { 0594 if( d->m_updating ) 0595 return; 0596 0597 d->m_updating = true; 0598 0599 d->m_inputHandler->handleUIntEditChange(); 0600 0601 d->m_updating = false; 0602 0603 recalculate(); 0604 } 0605 0606 void LatLonEdit::checkIntValueOverflow() 0607 { 0608 if( d->m_updating ) 0609 return; 0610 0611 d->m_updating = true; 0612 0613 d->m_inputHandler->handleIntEditChange(); 0614 0615 d->m_updating = false; 0616 0617 recalculate(); 0618 } 0619 0620 void LatLonEdit::setValue( qreal value ) 0621 { 0622 // limit to allowed values 0623 const qreal maxValue = (d->m_dimension == Longitude) ? 180.0 : 90.0; 0624 0625 if (value > maxValue) { 0626 value = maxValue; 0627 } else { 0628 const qreal minValue = -maxValue; 0629 if (value < minValue) { 0630 value = minValue; 0631 } 0632 } 0633 0634 // no change? 0635 if( value == d->m_value ) { 0636 return; 0637 } 0638 0639 d->m_value = value; 0640 0641 // calculate sub values 0642 // calculation is done similar to GeoDataCoordinates::lonToString, 0643 // perhaps should be moved with similar methods into some utility class/namespace 0644 0645 d->m_updating = true; 0646 0647 d->m_inputHandler->setValue(value); 0648 0649 const bool isNegative = (value < 0.0); 0650 d->m_sign->setCurrentIndex( isNegative ? NegativeSphereIndex : PositiveSphereIndex ); 0651 0652 d->m_updating = false; 0653 } 0654 0655 void LatLonEdit::recalculate() 0656 { 0657 d->m_value = d->m_inputHandler->calculateValue(); 0658 0659 emit valueChanged( d->m_value ); 0660 } 0661 0662 0663 #include "moc_LatLonEdit.cpp"