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

KDE's Doxygen guidelines are available online.