Marble

LatLonEdit.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2008 Henry de Valence <hdevalence@gmail.com>
4// SPDX-FileCopyrightText: 2011 Friedrich W. H. Kossebau <kossebau@kde.org>
5
6
7#include "LatLonEdit.h"
8#include "ui_LatLonEdit.h"
9
10#include "MarbleDebug.h"
11
12
13
14namespace Marble
15{
16
17// This widget can have 3 different designs, one per notation (Decimal, DMS, DM)
18// To reduce the footprint this was not implemented using a stack of widgets
19// where each widget offer the needed design for another notation.
20// Instead, as Decimal and DM are both using subsets of the UI elements used
21// for DMS, just the UI elements for DMS are created and modified as needed,
22// if another notation is selected. This involves showing and hiding them and
23// setting the proper suffix and min/max values.
24// The logic per notation is moved into specialized subclasses of a class
25// AbstractInputHandler.
26// TODO: simply remove the LatLonEdit.ui file and embed code directly here?
27
28enum { PositiveSphereIndex = 0, NegativeSphereIndex = 1 };
29
30
31class LatLonEditPrivate;
32
33class AbstractInputHandler // TODO: better name
34{
35protected:
36 explicit AbstractInputHandler(LatLonEditPrivate *ui) : m_ui(ui) {}
37public:
38 virtual ~AbstractInputHandler() {}
39
40public: // API to be implemented
41 virtual void setupUi() = 0;
42 virtual void setupMinMax(LatLonEdit::Dimension dimension) = 0;
43 virtual void setValue(qreal value) = 0;
44 virtual void handleIntEditChange() = 0;
45 virtual void handleUIntEditChange() = 0;
46 virtual void handleFloatEditChange() = 0;
47 virtual qreal calculateValue() const = 0;
48
49protected:
50 LatLonEditPrivate * const m_ui;
51};
52
53class DecimalInputHandler : public AbstractInputHandler
54{
55public:
56 explicit DecimalInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {}
57public: // AbstractInputHandler API
58 void setupUi() override;
59 void setupMinMax(LatLonEdit::Dimension dimension) override;
60 void setValue(qreal value) override;
61 void handleIntEditChange() override;
62 void handleUIntEditChange() override;
63 void handleFloatEditChange() override;
64 qreal calculateValue() const override;
65};
66
67class DMSInputHandler : public AbstractInputHandler
68{
69public:
70 explicit DMSInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {}
71public: // AbstractInputHandler API
72 void setupUi() override;
73 void setupMinMax(LatLonEdit::Dimension dimension) override;
74 void setValue(qreal value) override;
75 void handleIntEditChange() override;
76 void handleUIntEditChange() override;
77 void handleFloatEditChange() override;
78 qreal calculateValue() const override;
79};
80
81class DMInputHandler : public AbstractInputHandler
82{
83public:
84 explicit DMInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {}
85public: // AbstractInputHandler API
86 void setupUi() override;
87 void setupMinMax(LatLonEdit::Dimension dimension) override;
88 void setValue(qreal value) override;
89 void handleIntEditChange() override;
90 void handleUIntEditChange() override;
91 void handleFloatEditChange() override;
92
93 qreal calculateValue() const override;
94};
95
96class LatLonEditPrivate : public Ui::LatLonEditPrivate
97{
98 friend class DecimalInputHandler;
99 friend class DMSInputHandler;
100 friend class DMInputHandler;
101
102public:
103 LatLonEdit::Dimension m_dimension;
104 qreal m_value;
106 AbstractInputHandler *m_inputHandler;
107 // flag which indicates that the widgets are updated due to a change
108 // in one of the other widgets. Q*SpinBox does not have a signal which is
109 // only emitted by a change due to user input, not code setting a new value.
110 // This flag should be less expensive then disconnecting from and reconnecting
111 // to the valueChanged signal of all widgets.
112 bool m_updating : 1;
113
114 LatLonEditPrivate();
115 ~LatLonEditPrivate();
116 void init(QWidget* parent);
117};
118
119
120static void
121switchSign( QComboBox *sign )
122{
123 const bool isNegativeSphere = (sign->currentIndex() == NegativeSphereIndex);
124 sign->setCurrentIndex( isNegativeSphere ? PositiveSphereIndex : NegativeSphereIndex );
125}
126
127void DecimalInputHandler::setupUi()
128{
129 m_ui->m_floatValueEditor->setSuffix(LatLonEdit::tr("\xC2\xB0")); // the degree symbol °
130 m_ui->m_floatValueEditor->setDecimals(5);
131
132 m_ui->m_intValueEditor->hide();
133 m_ui->m_uintValueEditor->hide();
134}
135
136void DecimalInputHandler::setupMinMax(LatLonEdit::Dimension dimension)
137{
138 const qreal maxValue = (dimension == LatLonEdit::Longitude) ? 180.0 : 90.0;
139
140 m_ui->m_floatValueEditor->setMinimum(-maxValue);
141 m_ui->m_floatValueEditor->setMaximum( maxValue);
142}
143
144void DecimalInputHandler::setValue(qreal value)
145{
146 value = qAbs(value);
147
148 m_ui->m_floatValueEditor->setValue(value);
149}
150
151void DecimalInputHandler::handleIntEditChange()
152{
153 // nothing to do, perhaps rather disconnect the signal with this notation
154}
155
156void DecimalInputHandler::handleUIntEditChange()
157{
158 // nothing to do, perhaps rather disconnect the signal with this notation
159}
160
161void DecimalInputHandler::handleFloatEditChange()
162{
163 // nothing to do, perhaps rather disconnect the signal with this notation
164}
165
166qreal DecimalInputHandler::calculateValue() const
167{
168 qreal value = m_ui->m_floatValueEditor->value();
169
170 if (m_ui->m_sign->currentIndex() == NegativeSphereIndex) {
171 value *= -1;
172 }
173
174 return value;
175}
176
177void DMSInputHandler::setupUi()
178{
179 m_ui->m_uintValueEditor->setSuffix(LatLonEdit::tr("'"));
180 m_ui->m_floatValueEditor->setSuffix(LatLonEdit::tr("\""));
181 m_ui->m_floatValueEditor->setDecimals(2);
182
183 m_ui->m_intValueEditor->show();
184 m_ui->m_uintValueEditor->show();
185}
186
187void DMSInputHandler::setupMinMax(LatLonEdit::Dimension dimension)
188{
189 const int maxValue = (dimension == LatLonEdit::Longitude) ? 180 : 90;
190
191 m_ui->m_intValueEditor->setMinimum(-maxValue);
192 m_ui->m_intValueEditor->setMaximum( maxValue);
193}
194
195void DMSInputHandler::setValue(qreal value)
196{
197 value = qAbs( value );
198
199 int degValue = (int) value;
200
201 qreal minFValue = 60 * (value - degValue);
202 int minValue = (int) minFValue;
203 qreal secFValue = 60 * (minFValue - minValue);
204 // Adjustment for fuzziness (like 49.999999999999999999999)
205 int secValue = qRound(secFValue);
206 if (secValue > 59) {
207 secFValue = 0.0;
208 ++minValue;
209 }
210 if (minValue > 59) {
211 minValue = 0;
212 ++degValue;
213 }
214
215 m_ui->m_intValueEditor->setValue( degValue );
216 m_ui->m_uintValueEditor->setValue( minValue );
217 m_ui->m_floatValueEditor->setValue( secFValue );
218}
219
220void DMSInputHandler::handleIntEditChange()
221{
222 const int degValue = m_ui->m_intValueEditor->value();
223 const int minDegValue = m_ui->m_intValueEditor->minimum();
224 const int maxDegValue = m_ui->m_intValueEditor->maximum();
225 // at max/min?
226 if (degValue <= minDegValue || maxDegValue <= degValue) {
227 m_ui->m_uintValueEditor->setValue( 0 );
228 m_ui->m_floatValueEditor->setValue( 0.0 );
229 }
230}
231
232void DMSInputHandler::handleUIntEditChange()
233{
234 const int degValue = m_ui->m_intValueEditor->value();
235 const int minValue = m_ui->m_uintValueEditor->value();
236
237 if (minValue < 0) {
238 if (degValue != 0) {
239 m_ui->m_uintValueEditor->setValue( 59 );
240 const int degDec = (degValue > 0) ? 1 : -1;
241 m_ui->m_intValueEditor->setValue( degValue - degDec );
242 } else {
243 switchSign( m_ui->m_sign );
244 m_ui->m_uintValueEditor->setValue( 1 );
245 }
246 } else {
247 const int minDegValue = m_ui->m_intValueEditor->minimum();
248 const int maxDegValue = m_ui->m_intValueEditor->maximum();
249 // at max/min already?
250 if (degValue <= minDegValue || maxDegValue <= degValue) {
251 // ignore
252 m_ui->m_uintValueEditor->setValue( 0 );
253 // overflow?
254 } else if (minValue >= 60) {
255 m_ui->m_uintValueEditor->setValue( 0 );
256 // will reach max/min?
257 if (minDegValue+1 == degValue || degValue == maxDegValue-1) {
258 // reset also sec
259 m_ui->m_floatValueEditor->setValue( 0.0 );
260 }
261 const int degInc = (degValue > 0) ? 1 : -1;
262 m_ui->m_intValueEditor->setValue( degValue + degInc );
263 }
264 }
265}
266
267void DMSInputHandler::handleFloatEditChange()
268{
269 const int degValue = m_ui->m_intValueEditor->value();
270 const int minValue = m_ui->m_uintValueEditor->value();
271 const qreal secValue = m_ui->m_floatValueEditor->value();
272
273 if (secValue < 0.0) {
274 const qreal secDiff = -secValue;
275 if (degValue == 0 && minValue == 0) {
276 switchSign( m_ui->m_sign );
277 m_ui->m_floatValueEditor->setValue( secDiff );
278 } else {
279 m_ui->m_floatValueEditor->setValue( 60.0 - secDiff );
280 if (minValue > 0) {
281 m_ui->m_uintValueEditor->setValue( minValue - 1 );
282 } else {
283 m_ui->m_uintValueEditor->setValue( 59 );
284 const int degDec = (degValue > 0) ? 1 : -1;
285 m_ui->m_intValueEditor->setValue( degValue - degDec );
286 }
287 }
288 } else {
289 const int minDegValue = m_ui->m_intValueEditor->minimum();
290 const int maxDegValue = m_ui->m_intValueEditor->maximum();
291 // at max/min already?
292 if (degValue <= minDegValue || maxDegValue <= degValue) {
293 // ignore
294 m_ui->m_floatValueEditor->setValue( 0.0 );
295 // need to inc minutes?
296 } else if (secValue >= 60.0) {
297 qreal newSec = secValue - 60.0;
298 // will reach max/min?
299 if (minValue == 59) {
300 m_ui->m_uintValueEditor->setValue( 0 );
301 // will reach max/min?
302 if (minDegValue+1 == degValue || degValue == maxDegValue-1) {
303 // reset also sec
304 newSec = 0.0;
305 }
306 const int degInc = (degValue > 0) ? 1 : -1;
307 m_ui->m_intValueEditor->setValue( degValue + degInc );
308 } else {
309 m_ui->m_uintValueEditor->setValue( minValue + 1 );
310 }
311 m_ui->m_floatValueEditor->setValue( newSec );
312 }
313 }
314}
315
316qreal DMSInputHandler::calculateValue() const
317{
318 const bool isNegativeDeg = ( m_ui->m_intValueEditor->value() < 0 );
319
320 const qreal deg = (qreal)(qAbs(m_ui->m_intValueEditor->value()));
321 const qreal min = (qreal)(m_ui->m_uintValueEditor->value()) / 60.0;
322 const qreal sec = m_ui->m_floatValueEditor->value() / 3600.0;
323
324 qreal value = deg + min + sec;
325
326 if (isNegativeDeg) {
327 value *= -1;
328 }
329 if (m_ui->m_sign->currentIndex() == NegativeSphereIndex) {
330 value *= -1;
331 }
332
333 return value;
334}
335
336void DMInputHandler::setupUi()
337{
338 m_ui->m_floatValueEditor->setSuffix(LatLonEdit::tr("'"));
339 m_ui->m_floatValueEditor->setDecimals(2);
340
341 m_ui->m_intValueEditor->show();
342 m_ui->m_uintValueEditor->hide();
343}
344
345void DMInputHandler::setupMinMax(LatLonEdit::Dimension dimension)
346{
347 const int maxValue = (dimension == LatLonEdit::Longitude) ? 180 : 90;
348
349 m_ui->m_intValueEditor->setMinimum(-maxValue);
350 m_ui->m_intValueEditor->setMaximum( maxValue);
351}
352
353void DMInputHandler::setValue(qreal value)
354{
355 value = qAbs(value);
356
357 int degValue = (int)value;
358
359 qreal minFValue = 60 * (value - degValue);
360 // Adjustment for fuzziness (like 49.999999999999999999999)
361 int minValue = qRound( minFValue );
362 if (minValue > 59) {
363 minFValue = 0.0;
364 ++degValue;
365 }
366
367 m_ui->m_intValueEditor->setValue( degValue );
368 m_ui->m_floatValueEditor->setValue( minFValue );
369}
370
371void DMInputHandler::handleIntEditChange()
372{
373 const int degValue = m_ui->m_intValueEditor->value();
374 const int minDegValue = m_ui->m_intValueEditor->minimum();
375 const int maxDegValue = m_ui->m_intValueEditor->maximum();
376 // at max/min?
377 if (degValue <= minDegValue || maxDegValue <= degValue) {
378 m_ui->m_floatValueEditor->setValue( 0.0 );
379 }
380}
381
382void DMInputHandler::handleUIntEditChange()
383{
384 // nothing to be done here, should be never called
385}
386
387void DMInputHandler::handleFloatEditChange()
388{
389 const int degValue = m_ui->m_intValueEditor->value();
390 const qreal minValue = m_ui->m_floatValueEditor->value();
391
392 if (minValue < 0.0) {
393 const qreal minDiff = -minValue;
394 if (degValue == 0) {
395 switchSign( m_ui->m_sign );
396 m_ui->m_floatValueEditor->setValue( minDiff );
397 } else {
398 m_ui->m_floatValueEditor->setValue( 60.0 - minDiff );
399 m_ui->m_intValueEditor->setValue( degValue - 1 );
400 }
401 } else {
402 const int minDegValue = m_ui->m_intValueEditor->minimum();
403 const int maxDegValue = m_ui->m_intValueEditor->maximum();
404 // at max/min already?
405 if (degValue <= minDegValue || maxDegValue <= degValue) {
406 // ignore
407 m_ui->m_floatValueEditor->setValue( 0.0 );
408 // need to inc degrees?
409 } else if (minValue >= 60.0) {
410 qreal newMin = minValue - 60.0;
411 // will reach max/min?
412 if (minDegValue+1 == degValue || degValue == maxDegValue-1) {
413 // reset also sec
414 newMin = 0.0;
415 } else {
416 m_ui->m_intValueEditor->setValue( degValue + 1 );
417 }
418 m_ui->m_floatValueEditor->setValue( newMin );
419 }
420 }
421}
422
423qreal DMInputHandler::calculateValue() const
424{
425 const bool isNegativeDeg = ( m_ui->m_intValueEditor->value() < 0 );
426
427 const qreal deg = (qreal)(qAbs(m_ui->m_intValueEditor->value()));
428 const qreal min = m_ui->m_floatValueEditor->value() / 60.0;
429
430 qreal value = deg + min;
431
432 if (isNegativeDeg) {
433 value *= -1;
434 }
435 if (m_ui->m_sign->currentIndex() == NegativeSphereIndex) {
436 value *= -1;
437 }
438
439 return value;
440}
441
442
443LatLonEditPrivate::LatLonEditPrivate()
444 : m_dimension(LatLonEdit::Latitude)
445 , m_value(0.0)
446 , m_notation(GeoDataCoordinates::DMS)
447 , m_inputHandler(new DMSInputHandler(this))
448 , m_updating(false)
449{}
450
451LatLonEditPrivate::~LatLonEditPrivate()
452{
453 delete m_inputHandler;
454}
455
456void LatLonEditPrivate::init(QWidget* parent) { setupUi(parent); }
457
458}
459
460
461using namespace Marble;
462
463LatLonEdit::LatLonEdit(QWidget *parent, LatLonEdit::Dimension dimension, GeoDataCoordinates::Notation notation)
464 : QWidget( parent ), d(new LatLonEditPrivate())
465{
466 d->init(this);
467 setDimension(dimension);
468 setNotation(notation);
469
470 connect(d->m_intValueEditor, SIGNAL(valueChanged(int)), this, SLOT(checkIntValueOverflow()));
471 connect(d->m_uintValueEditor, SIGNAL(valueChanged(int)), this, SLOT(checkUIntValueOverflow()));
472 connect(d->m_floatValueEditor, SIGNAL(valueChanged(double)), this, SLOT(checkFloatValueOverflow()));
473
474 connect(d->m_sign, SIGNAL(currentIndexChanged(int)),
475 this, SLOT(onSignChanged()));
476}
477
478LatLonEdit::~LatLonEdit()
479{
480 delete d;
481}
482
483qreal LatLonEdit::value() const
484{
485 return d->m_value;
486}
487
488GeoDataCoordinates::Notation LatLonEdit::notation() const
489{
490 return d->m_notation;
491}
492
493void LatLonEdit::onSignChanged()
494{
495 if( d->m_updating )
496 return;
497
498 // Only flip the value if it does not match the sign
499 if (d->m_sign->currentIndex() == PositiveSphereIndex) {
500 if (d->m_value < 0.0) {
501 d->m_value *= -1;
502 }
503 } else {
504 if (d->m_value > 0.0) {
505 d->m_value *= -1;
506 }
507 }
508
509 emit valueChanged( d->m_value );
510}
511
512void LatLonEdit::setDimension( Dimension dimension )
513{
514 d->m_dimension = dimension;
515
516 d->m_updating = true;
517
518 d->m_inputHandler->setupMinMax(dimension);
519
520 // update sign widget content
521 {
522 d->m_sign->clear();
523
524 switch (dimension) {
525 case Longitude:
526 d->m_sign->addItem( tr("E", "East, the direction" ) );
527 d->m_sign->addItem( tr("W", "West, the direction" ) );
528 break;
529 case Latitude:
530 d->m_sign->addItem( tr("N", "North, the direction" ) );
531 d->m_sign->addItem( tr("S", "South, the direction" ) );
532 break;
533 }
534 }
535
536 d->m_updating = false;
537
538 // reset value, old one is useless
539 setValue( 0.0 );
540}
541
542void LatLonEdit::setNotation(GeoDataCoordinates::Notation notation)
543{
544 delete d->m_inputHandler;
545 d->m_inputHandler = nullptr;
546
547 switch (notation) {
549 d->m_inputHandler = new DecimalInputHandler(d);
550 break;
552 d->m_inputHandler = new DMSInputHandler(d);
553 break;
555 d->m_inputHandler = new DMInputHandler(d);
556 break;
557 case GeoDataCoordinates::UTM:
558 /** @todo see below */
559 break;
561 /** @todo see below */
562 break;
563 }
564
565 if (!d->m_inputHandler) {
566 /** @todo Temporary fallback to DecimalInputHandler
567 * Implement proper handlers for UTM and Astro */
568 d->m_inputHandler = new DecimalInputHandler(d);
569 }
570
571 d->m_notation = notation;
572 d->m_inputHandler->setupUi();
573 d->m_inputHandler->setupMinMax(d->m_dimension);
574 d->m_inputHandler->setValue(d->m_value);
575}
576
577void LatLonEdit::checkFloatValueOverflow()
578{
579 if( d->m_updating )
580 return;
581
582 d->m_updating = true;
583
584 d->m_inputHandler->handleFloatEditChange();
585
586 d->m_updating = false;
587
588 recalculate();
589}
590
591
592void LatLonEdit::checkUIntValueOverflow()
593{
594 if( d->m_updating )
595 return;
596
597 d->m_updating = true;
598
599 d->m_inputHandler->handleUIntEditChange();
600
601 d->m_updating = false;
602
603 recalculate();
604}
605
606void LatLonEdit::checkIntValueOverflow()
607{
608 if( d->m_updating )
609 return;
610
611 d->m_updating = true;
612
613 d->m_inputHandler->handleIntEditChange();
614
615 d->m_updating = false;
616
617 recalculate();
618}
619
620void LatLonEdit::setValue( qreal value )
621{
622 // limit to allowed values
623 const qreal maxValue = (d->m_dimension == Longitude) ? 180.0 : 90.0;
624
625 if (value > maxValue) {
626 value = maxValue;
627 } else {
628 const qreal minValue = -maxValue;
629 if (value < minValue) {
630 value = minValue;
631 }
632 }
633
634 // no change?
635 if( value == d->m_value ) {
636 return;
637 }
638
639 d->m_value = value;
640
641 // calculate sub values
642 // calculation is done similar to GeoDataCoordinates::lonToString,
643 // perhaps should be moved with similar methods into some utility class/namespace
644
645 d->m_updating = true;
646
647 d->m_inputHandler->setValue(value);
648
649 const bool isNegative = (value < 0.0);
650 d->m_sign->setCurrentIndex( isNegative ? NegativeSphereIndex : PositiveSphereIndex );
651
652 d->m_updating = false;
653}
654
655void LatLonEdit::recalculate()
656{
657 d->m_value = d->m_inputHandler->calculateValue();
658
659 emit valueChanged( d->m_value );
660}
661
662
663#include "moc_LatLonEdit.cpp"
A 3d point representation.
Notation
enum used to specify the notation / numerical system
@ Astro
< "RA and DEC" notation (used for astronomical sky coordinates)
@ DM
"Sexagesimal DM" notation (base-60)
@ DMS
"Sexagesimal DMS" notation (base-60)
@ Decimal
"Decimal" notation (base-10)
Binds a QML item to a specific geodetic location in screen coordinates.
T qobject_cast(QObject *object)
QString tr(const char *sourceText, const char *disambiguation, int n)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.