File indexing completed on 2024-04-21 03:49:38

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"