FlightGear next
UnitsModel.cxx
Go to the documentation of this file.
1// UnitsModel.cxx - part of GUI launcher using Qt5
2//
3// Written by James Turner, started July 2018
4//
5// Copyright (C) 2018 James Turner <james@flightgear.org>
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#include "UnitsModel.hxx"
22
23#include <cmath>
24#include <cassert>
25#include <algorithm>
26
27#include <simgear/constants.h>
28
29#include <QIntValidator>
30#include <QDoubleValidator>
31#include <QDataStream>
32#include <QDebug>
33#include <QGuiApplication>
34
35namespace
36{
37
38struct UnitData
39{
40 UnitData(const char* sn, const char* ln, QString metrics, bool pfx = false) :
41 shortName(sn), longName(ln),
42 maxTextForMetrics(metrics),
43 isPrefix(pfx) {}
44
45 UnitData(const char* sn, const char* ln,
46 QString metrics,
47 bool pfx,
48 double min, double max,
49 double step = 1.0,
50 bool wraps = false,
51 int dps = 0) :
52 shortName(sn), longName(ln),
53 maxTextForMetrics(metrics),
54 isPrefix(pfx),
55 valueWraps(wraps),
56 minValue(min), maxValue(max), stepSize(step),
57 decimals(dps)
58 {}
59
60 const char* shortName;
61 const char* longName;
62 QString maxTextForMetrics;
63 bool isPrefix = false;
64 bool valueWraps = false;
65 double minValue = 0.0;
66 double maxValue = 9999999.0;
67 double stepSize = 1.0;
68 int decimals = 0;
69};
70
71std::vector<UnitData> static_unitData = {
72 { "", "", "" }, // noUnits
73 { QT_TRANSLATE_NOOP("UnitsModel", "ft"), QT_TRANSLATE_NOOP("UnitsModel", "feet above sea-level (MSL)"), "000000", false, -2000, 180000, 50},
74 { QT_TRANSLATE_NOOP("UnitsModel", "ft AGL"), QT_TRANSLATE_NOOP("UnitsModel", "feet above ground level (AGL)"), "000000", false, 0, 180000, 50},
75 { QT_TRANSLATE_NOOP("UnitsModel", "ft above field"), QT_TRANSLATE_NOOP("UnitsModel", "feet above airfield"), "000000", false, 0, 180000, 50},
76 { QT_TRANSLATE_NOOP("UnitsModel", "FL"), QT_TRANSLATE_NOOP("UnitsModel", "Flight-level"), "000", true /* prefix */, 0.0, 500.0, 5.0},
77 { QT_TRANSLATE_NOOP("UnitsModel", "m"), QT_TRANSLATE_NOOP("UnitsModel", "meters above sea-level (MSL)"), "000000", false, -500, 100000, 50},
78 { QT_TRANSLATE_NOOP("UnitsModel", "kts"), QT_TRANSLATE_NOOP("UnitsModel", "Knots"), "9999", false, 0, 999999, 10.0},
79 { QT_TRANSLATE_NOOP("UnitsModel", "M"), QT_TRANSLATE_NOOP("UnitsModel", "Mach"), "00.000", true /* prefix */, 0.0, 99.0, 0.05, false /* no wrap */, 3 /* decimal places */},
80 { QT_TRANSLATE_NOOP("UnitsModel", "KM/H"), QT_TRANSLATE_NOOP("UnitsModel", "Kilometers/hour"), "9999", false, 0, 999999, 10.0},
81 { QT_TRANSLATE_NOOP("UnitsModel", "°True"), QT_TRANSLATE_NOOP("UnitsModel", "degrees true"), "000", false, 0, 359, 5.0, true /* wraps */},
82 { QT_TRANSLATE_NOOP("UnitsModel", "°Mag"), QT_TRANSLATE_NOOP("UnitsModel", "degrees magnetic"), "000", false, 0, 359, 5.0, true /* wraps */},
83 { QT_TRANSLATE_NOOP("UnitsModel", "UTC"), QT_TRANSLATE_NOOP("UnitsModel", "Universal coordinated time"), ""},
84 { QT_TRANSLATE_NOOP("UnitsModel", "Local"), QT_TRANSLATE_NOOP("UnitsModel", "Local time"), ""},
85 { QT_TRANSLATE_NOOP("UnitsModel", "Nm"), QT_TRANSLATE_NOOP("UnitsModel", "Nautical miles"), "00000", false, 0, 999999, 1.0, false /* no wrap */, 1 /* decimal places */},
86 { QT_TRANSLATE_NOOP("UnitsModel", "Km"), QT_TRANSLATE_NOOP("UnitsModel", "Kilometers"), "00000", false, 0, 999999, 1.0, false /* no wrap */, 1 /* decimal places */},
87
88 { QT_TRANSLATE_NOOP("UnitsModel", "MHz"), QT_TRANSLATE_NOOP("UnitsModel", "MHz"), "00000", false, 105, 140, 0.025, false /* no wrap */, 3 /* decimal places */},
89 { QT_TRANSLATE_NOOP("UnitsModel", "kHz"), QT_TRANSLATE_NOOP("UnitsModel", "kHz"), "00000", false, 200, 400, 1.0, false /* no wrap */, 0 /* decimal places */}
90
91};
92
93// order here corresponds to the Mode enum
94std::vector<UnitsModel::UnitVec> static_modeData = {
105};
106
107const int UnitLongNameRole = Qt::UserRole + 1;
108const int UnitIsPrefixRole = Qt::UserRole + 2;
109const int UnitMinValueRole = Qt::UserRole + 3;
110const int UnitMaxValueRole = Qt::UserRole + 4;
111const int UnitStepSizeRole = Qt::UserRole + 5;
112const int UnitDecimalsRole = Qt::UserRole + 6;
113const int UnitValueWrapsRole = Qt::UserRole + 7;
114
115} // of anonymous namespace
116
118{
119
120
121 m_enabledUnits = static_modeData.at(m_mode);
122}
123
124int UnitsModel::rowCount(const QModelIndex &) const
125{
126 return static_cast<int>(m_enabledUnits.size());
127}
128
129QVariant UnitsModel::data(const QModelIndex &index, int role) const
130{
131 int row = index.row();
132 if ((row < 0) || (static_cast<size_t>(row) >= m_enabledUnits.size()))
133 return {};
134
135 const Units::Type u = m_enabledUnits.at(row);
136 const UnitData& ud = static_unitData.at(u);
137
138 switch (role) {
139 case Qt::DisplayRole:
140 return qApp->translate("UnitsModel", ud.shortName);
141
142 case UnitLongNameRole:
143 return qApp->translate("UnitsModel", ud.longName);
144
145 case UnitIsPrefixRole: return ud.isPrefix;
146 case UnitMinValueRole: return ud.minValue;
147 case UnitMaxValueRole: return ud.maxValue;
148 case UnitStepSizeRole: return ud.stepSize;
149 case UnitValueWrapsRole: return ud.valueWraps;
150 case UnitDecimalsRole: return ud.decimals;
151 default:
152 break;
153 }
154
155 return {};
156}
157
158QValidator* UnitsModel::validator() const
159{
160 const auto u = m_enabledUnits.at(m_activeIndex);
161 const UnitData& ud = static_unitData.at(u);
162 if (ud.decimals > 0) {
163 return new QDoubleValidator(ud.minValue, ud.maxValue, ud.decimals);
164 }
165
166 if ((u == Units::TimeLocal) || (u == Units::TimeUTC)) {
167 return nullptr; // no validation
168 }
169
170 return new QIntValidator(static_cast<int>(ud.minValue),
171 static_cast<int>(ud.maxValue));
172}
173
175{
176 const auto u = m_enabledUnits.at(m_activeIndex);
177 const UnitData& ud = static_unitData.at(u);
178 return ud.maxTextForMetrics;
179}
180
182{
183 const auto u = m_enabledUnits.at(m_activeIndex);
184 const UnitData& ud = static_unitData.at(u);
185 return ud.isPrefix;
186}
187
189{
190 const auto u = m_enabledUnits.at(m_activeIndex);
191 const UnitData& ud = static_unitData.at(u);
192 return ud.valueWraps;
193}
194
196{
197 const auto u = m_enabledUnits.at(m_activeIndex);
198 const UnitData& ud = static_unitData.at(u);
199 return qApp->translate("UnitsModel",ud.shortName);
200}
201
203{
204 return m_enabledUnits.at(m_activeIndex);
205}
206
208{
209 return static_cast<int>(m_enabledUnits.size());
210}
211
212bool UnitsModel::isUnitInMode(int unit) const
213{
214 auto it = std::find(m_enabledUnits.begin(), m_enabledUnits.end(), unit);
215 return it != m_enabledUnits.end();
216}
217
218QHash<int, QByteArray> UnitsModel::roleNames() const
219{
220 QHash<int, QByteArray> result = QAbstractListModel::roleNames();
221
222 result[Qt::DisplayRole] = "shortName";
223 result[UnitLongNameRole] = "longName";
224 result[UnitIsPrefixRole] = "isPrefix";
225 result[UnitValueWrapsRole] = "valueDoesWrap";
226 result[UnitMinValueRole] = "minValue";
227 result[UnitMaxValueRole] = "maxValue";
228 result[UnitStepSizeRole] = "stepSize";
229 result[UnitDecimalsRole] = "decimalPlaces";
230
231 return result;
232}
233
235{
236 const auto u = m_enabledUnits.at(m_activeIndex);
237 const UnitData& ud = static_unitData.at(u);
238 return ud.decimals;
239}
240
242{
243 const auto u = m_enabledUnits.at(m_activeIndex);
244 const UnitData& ud = static_unitData.at(u);
245 return ud.minValue;
246}
247
249{
250 const auto u = m_enabledUnits.at(m_activeIndex);
251 const UnitData& ud = static_unitData.at(u);
252 return ud.maxValue;
253}
254
256{
257 const auto u = m_enabledUnits.at(m_activeIndex);
258 const UnitData& ud = static_unitData.at(u);
259 return ud.stepSize;
260}
261
263{
264 if (m_mode == mode)
265 return;
266
267 m_mode = mode;
268 emit modeChanged(m_mode);
269
270 beginResetModel();
271 m_enabledUnits = static_modeData.at(mode);
272 endResetModel();
273}
274
276{
277 if (m_activeIndex == static_cast<quint32>(selectedIndex))
278 return;
279
280 if ((selectedIndex < 0) || (static_cast<size_t>(selectedIndex) >= m_enabledUnits.size()))
281 return;
282
283 m_activeIndex = selectedIndex;
284 emit selectionChanged(m_activeIndex);
285}
286
288{
289 auto it = std::find(m_enabledUnits.begin(), m_enabledUnits.end(), static_cast<Units::Type>(u));
290 if (it == m_enabledUnits.end()) {
291 qWarning() << Q_FUNC_INFO << "unit" << u << "not enabled for mode" << m_mode;
292 return;
293 }
294
295 auto index = std::distance(m_enabledUnits.begin(), it);
296 if (index != m_activeIndex) {
297 m_activeIndex = static_cast<quint32>(index);
298 emit selectionChanged(m_activeIndex);
299 }
300}
301
305
307 value(v),
308 unit(u)
309{
310}
311
313 value(static_cast<double>(v)),
314 unit(u)
315{
316 assert(static_unitData.at(u).decimals == 0);
317}
318
320{
321 // special case a no-change
322 if (unit == u)
323 return *this;
324
325 if (unit == Units::NoUnits) {
326 return {u, 0.0};
327 }
328
329 switch (u) {
331 {
332 if (unit == Units::Kilometers) {
333 return {u, value * SG_METER_TO_NM * 1000};
334 }
335 break;
336 }
337
339 {
340 if (unit == Units::NauticalMiles) {
341 return {u, value * SG_NM_TO_METER * 0.001};
342 }
343 break;
344 }
345
346 case Units::FeetMSL:
347 {
348 if (unit == Units::FlightLevel) {
349 return {u, value * 100};
350 } else if (unit == Units::MetersMSL) {
351 return {u, value * SG_METER_TO_FEET};
352 } else if ((unit == Units::FeetAGL) || (unit == Units::FeetAboveFieldElevation)) {
353 return {u, value};
354 }
355 break;
356 }
357
359 case Units::FeetAGL:
360 // treat as FeetMSL
362
363 case Units::MetersMSL:
364 return {u, convertToUnit(Units::FeetMSL).value * SG_FEET_TO_METER};
365
366 case Units::Mach:
367 {
368 if (unit == Units::Knots) {
369 // obviously this depends on altitude, let's
370 // use the value at sea level for now
371 return {u, value / 667.0};
372 } else if (unit == Units::KilometersPerHour) {
374 }
375 break;
376 }
377
378 case Units::Knots:
379 {
380 if (unit == Units::Mach) {
381 // obviously this depends on altitude, let's
382 // use the value at sea level for now
383 return {u, value * 667.0};
384 } else if (unit == Units::KilometersPerHour) {
385 return {u, value * SG_KMH_TO_MPS * SG_MPS_TO_KT};
386 }
387 break;
388 }
389
391 if (unit == Units::Knots) {
392 return {u, value * SG_KT_TO_MPS * SG_MPS_TO_KMH};
393 } else {
394 // round-trip via Knots
396 }
397
400 {
401 // we don't have a location to apply mag-var, so just keep the
402 // current value
404 return {u, value};
405 }
406 break;
407 }
408
410 if (unit == Units::FeetMSL) {
411 return {u, static_cast<double>(static_cast<int>(value / 100))};
412 } else {
414 }
415
416 default:
417 qWarning() << Q_FUNC_INFO << "unhandled case:" << u << "from" << unit;
418 break;
419 }
420
421 return {};
422}
423
425{
426 return convertToUnit(static_cast<Units::Type>(u));
427}
428
430{
431 if (unit == Units::NoUnits)
432 return "<no unit>";
433
434 const auto& data = static_unitData.at(unit);
435 int dp = data.decimals;
436 QString prefix;
437 QString suffix = qApp->translate("UnitsModel", data.shortName);
438 if (data.isPrefix)
439 std::swap(prefix, suffix);
440
441 if (dp == 0) {
442 return prefix + QString::number(static_cast<int>(value)) + suffix;
443 }
444
445 return prefix + QString::number(value, 'f', dp) + suffix;
446}
447
449{
450 return unit != Units::NoUnits;
451}
452
454{
455 if (v.unit != unit)
456 return false;
457
458 int dp = static_unitData.at(unit).decimals;
459 const auto aInt = static_cast<qlonglong>(value * pow(10, dp));
460 const auto bInt = static_cast<qlonglong>(v.value * pow(10, dp));
461 return aInt == bInt;
462}
463
465{
466 return !(*this == v);
467}
468
469QDataStream &operator<<(QDataStream &out, const QuantityValue &value)
470{
471 out << static_cast<quint8>(value.unit);
472 if (value.unit != Units::NoUnits)
473 out << value.value;
474 return out;
475}
476
477QDataStream &operator>>(QDataStream &in, QuantityValue &value)
478{
479 quint8 unit;
480 in >> unit;
481 value.unit = static_cast<Units::Type>(unit);
482 if (unit != Units::NoUnits)
483 in >> value.value;
484 return in;
485}
#define min(X, Y)
QDataStream & operator<<(QDataStream &out, const QuantityValue &value)
QDataStream & operator>>(QDataStream &in, QuantityValue &value)
Q_INVOKABLE QString toString() const
bool operator!=(const QuantityValue &v) const
Units::Type unit
bool operator==(const QuantityValue &v) const
QuantityValue convertToUnit(Units::Type u) const
Q_INVOKABLE bool isValid() const
Units::Mode mode
int rowCount(const QModelIndex &parent) const override
double stepSize
Q_INVOKABLE bool isUnitInMode(int unit) const
QString maxTextForMetrics
void selectionChanged(int selectedIndex)
void setSelectedUnit(int u)
QValidator * validator
void setSelectedIndex(int selectedIndex)
double minValue
QVariant data(const QModelIndex &index, int role) const override
QString shortText
void modeChanged(Units::Mode mode)
double maxValue
QHash< int, QByteArray > roleNames() const override
void setMode(Units::Mode mode)
bool doesWrap() const
Type
This enum stores units / types of values used in the simulator.
@ KilometersPerHour
@ TimeLocal
@ DegreesTrue
@ FlightLevel
@ MetersMSL
@ FeetAboveFieldElevation
@ Kilometers
@ NauticalMiles
@ DegreesMagnetic