Marble

LatLonEdit.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2008 Henry de Valence <[email protected]>
4 // SPDX-FileCopyrightText: 2011 Friedrich W. H. Kossebau <[email protected]>
5 
6 
7 #include "LatLonEdit.h"
8 #include "ui_LatLonEdit.h"
9 
10 #include "MarbleDebug.h"
11 
12 
13 
14 namespace 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 
28 enum { PositiveSphereIndex = 0, NegativeSphereIndex = 1 };
29 
30 
31 class LatLonEditPrivate;
32 
33 class AbstractInputHandler // TODO: better name
34 {
35 protected:
36  explicit AbstractInputHandler(LatLonEditPrivate *ui) : m_ui(ui) {}
37 public:
38  virtual ~AbstractInputHandler() {}
39 
40 public: // 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 
49 protected:
50  LatLonEditPrivate * const m_ui;
51 };
52 
53 class DecimalInputHandler : public AbstractInputHandler
54 {
55 public:
56  explicit DecimalInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {}
57 public: // 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 
67 class DMSInputHandler : public AbstractInputHandler
68 {
69 public:
70  explicit DMSInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {}
71 public: // 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 
81 class DMInputHandler : public AbstractInputHandler
82 {
83 public:
84  explicit DMInputHandler(LatLonEditPrivate *ui) : AbstractInputHandler(ui) {}
85 public: // 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 
96 class LatLonEditPrivate : public Ui::LatLonEditPrivate
97 {
98  friend class DecimalInputHandler;
99  friend class DMSInputHandler;
100  friend class DMInputHandler;
101 
102 public:
103  LatLonEdit::Dimension m_dimension;
104  qreal m_value;
105  GeoDataCoordinates::Notation m_notation;
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 
120 static void
121 switchSign( QComboBox *sign )
122 {
123  const bool isNegativeSphere = (sign->currentIndex() == NegativeSphereIndex);
124  sign->setCurrentIndex( isNegativeSphere ? PositiveSphereIndex : NegativeSphereIndex );
125 }
126 
127 void 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 
136 void 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 
144 void DecimalInputHandler::setValue(qreal value)
145 {
146  value = qAbs(value);
147 
148  m_ui->m_floatValueEditor->setValue(value);
149 }
150 
151 void DecimalInputHandler::handleIntEditChange()
152 {
153  // nothing to do, perhaps rather disconnect the signal with this notation
154 }
155 
156 void DecimalInputHandler::handleUIntEditChange()
157 {
158  // nothing to do, perhaps rather disconnect the signal with this notation
159 }
160 
161 void DecimalInputHandler::handleFloatEditChange()
162 {
163  // nothing to do, perhaps rather disconnect the signal with this notation
164 }
165 
166 qreal 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 
177 void 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 
187 void 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 
195 void 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 
220 void 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 
232 void 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 
267 void 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 
316 qreal 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 
336 void 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 
345 void 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 
353 void 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 
371 void 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 
382 void DMInputHandler::handleUIntEditChange()
383 {
384  // nothing to be done here, should be never called
385 }
386 
387 void 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 
423 qreal 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 
443 LatLonEditPrivate::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 
451 LatLonEditPrivate::~LatLonEditPrivate()
452 {
453  delete m_inputHandler;
454 }
455 
456 void LatLonEditPrivate::init(QWidget* parent) { setupUi(parent); }
457 
458 }
459 
460 
461 using namespace Marble;
462 
463 LatLonEdit::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 
478 LatLonEdit::~LatLonEdit()
479 {
480  delete d;
481 }
482 
483 qreal LatLonEdit::value() const
484 {
485  return d->m_value;
486 }
487 
488 GeoDataCoordinates::Notation LatLonEdit::notation() const
489 {
490  return d->m_notation;
491 }
492 
493 void 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 
512 void 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 
542 void 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 
577 void 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 
592 void 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 
606 void 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 
620 void 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 
655 void 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.
@ DMS
"Sexagesimal DMS" notation (base-60)
@ Astro
< "RA and DEC" notation (used for astronomical sky coordinates)
@ DM
"Sexagesimal DM" notation (base-60)
@ Decimal
"Decimal" notation (base-10)
Binds a QML item to a specific geodetic location in screen coordinates.
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
Notation
enum used to specify the notation / numerical system
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Oct 4 2023 04:09:42 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.