FlightGear next
QmlAircraftInfo.cxx
Go to the documentation of this file.
1#include "config.h"
2
3#include "QmlAircraftInfo.hxx"
4
5#include <QVariant>
6#include <QDebug>
7#include <QQmlEngine>
8#include <QQmlComponent>
9#include <QTimer>
10
11#include <simgear/package/Install.hxx>
12#include <simgear/package/Root.hxx>
13#include <simgear/structure/exception.hxx>
14#include <simgear/props/props_io.hxx>
15
16#include <Main/globals.hxx>
17
20
21using namespace simgear::pkg;
22using namespace std::chrono_literals;
23
24const int QmlAircraftInfo::StateTagRole = Qt::UserRole + 1;
25const int QmlAircraftInfo::StateDescriptionRole = Qt::UserRole + 2;
26const int QmlAircraftInfo::StateExplicitRole = Qt::UserRole + 3;
27
28class QmlAircraftInfo::Delegate : public simgear::pkg::Delegate
29{
30public:
32 p(info)
33 {
34 globals->packageRoot()->addDelegate(this);
35 }
36
37 ~Delegate() override
38 {
39 globals->packageRoot()->removeDelegate(this);
40 }
41
42protected:
43 void finishInstall(InstallRef aInstall, StatusCode aReason) override;
44
45 void catalogRefreshed(CatalogRef, StatusCode) override
46 {
47 }
48
49 void startInstall(InstallRef aInstall) override
50 {
51 if (aInstall->package() == p->packageRef()) {
52 p->setDownloadBytes(0);
53 }
54 }
55
56 void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total) override
57 {
58 Q_UNUSED(total)
59 if (aInstall->package() == p->packageRef()) {
60 p->setDownloadBytes(bytes);
61 }
62 }
63
64 void installStatusChanged(InstallRef aInstall, StatusCode aReason) override
65 {
66 Q_UNUSED(aReason)
67 if (aInstall->package() == p->packageRef()) {
68 p->downloadChanged();
69 }
70 }
71
72 void finishUninstall(const PackageRef& pkg) override
73 {
74 if (pkg == p->packageRef()) {
75 p->downloadChanged();
76 }
77 }
78
79private:
80
82};
83
85
86
87static bool readAircraftStates(const SGPropertyNode_ptr root, AircraftStateVec& result)
88{
89 result.clear();
90 if (!root->getNode("sim/state")) {
91 return false; // no states
92 }
93
94 auto nodes = root->getNode("sim")->getChildren("state");
95 result.reserve(nodes.size());
96 for (auto cn : nodes) {
97 string_list stateNames;
98 for (auto nameNode : cn->getChildren("name")) {
99 stateNames.push_back(nameNode->getStringValue());
100 }
101
102 if (stateNames.empty()) {
103 qWarning() << "state with no names defined, skipping" << QString::fromStdString(cn->getPath());
104 continue;
105 }
106
107 result.push_back({stateNames,
108 QString::fromStdString(cn->getStringValue("readable-name")),
109 QString::fromStdString(cn->getStringValue("description"))
110 });
111 }
112
113 return true;
114}
115
116QString humanNameFromStateTag(const std::string& tag)
117{
118 if (tag == "approach") return QObject::tr("On approach");
119 if ((tag == "take-off") || (tag == "takeoff"))
120 return QObject::tr("Ready for take-off");
121 if ((tag == "parked") || (tag == "parking") || (tag == "cold-and-dark"))
122 return QObject::tr("Parked, cold & dark");
123 if (tag == "auto")
124 return QObject::tr("Automatic");
125 if (tag == "cruise")
126 return QObject::tr("Cruise");
127 if (tag == "taxi")
128 return QObject::tr("Ready to taxi");
129 if (tag == "carrier-approach")
130 return QObject::tr("On approach to a carrier");
131 if (tag == "carrier-take-off")
132 return QObject::tr("Ready for catapult launch");
133
134 qWarning() << Q_FUNC_INFO << "add translation / string for" << QString::fromStdString(tag);
135 // no mapping, let's use the tag directly
136 return QString::fromStdString(tag);
137}
138
140
141
142StatesModel::StatesModel(QObject* pr) : QAbstractListModel(pr)
143{
144}
145
147{
148 beginResetModel();
149 _data.clear();
150 _explicitAutoState = false;
151 endResetModel();
152}
153
155{
156 beginResetModel();
157 _data = states;
158 _explicitAutoState = false;
159
160 // we use an empty model for aircraft with no states defined
161 if (states.empty()) {
162 endResetModel();
163 return;
164 }
165
166 // sort which places 'auto' item at the front if it exists
167 std::sort(_data.begin(), _data.end(), [](const AircraftStateInfo& a, const AircraftStateInfo& b) {
168 if (a.primaryTag() == "auto") return true;
169 if (b.primaryTag() == "auto") return false;
170 return a.primaryTag() < b.primaryTag();
171 });
172
173 if (_data.front().primaryTag() == "auto") {
174 // track if the aircraft supplied an 'auto' state, in which case
175 // we will not run our own selection logic
176 _explicitAutoState = true;
177 } else {
178 _data.insert(_data.begin(), {{"auto"}, {}, tr("Select state based on startup position.")});
179 }
180
181 endResetModel();
182}
183
184int StatesModel::indexForTag(QString s) const
185{
186 return indexForTag(s.toStdString());
187}
188
189int StatesModel::indexForTag(const std::string& tag) const
190{
191 auto it = std::find_if(_data.begin(), _data.end(), [tag](const AircraftStateInfo& i) {
192 auto tagIt = std::find(i.tags.begin(), i.tags.end(), tag);
193 return (tagIt != i.tags.end());
194 });
195
196 if (it == _data.end())
197 return -1;
198
199 return static_cast<int>(std::distance(_data.begin(), it));
200}
201
202int StatesModel::rowCount(const QModelIndex&) const
203{
204 return static_cast<int>(_data.size());
205}
206
207QVariant StatesModel::data(const QModelIndex& index, int role) const
208{
209 size_t i;
210 if (!makeSafeIndex(index.row(), i))
211 return {};
212 const auto& s = _data.at(i);
213 if (role == Qt::DisplayRole) {
214 if (s.name.isEmpty()) {
215 return humanNameFromStateTag(s.primaryTag());
216 }
217 return s.name;
218 } else if (role == QmlAircraftInfo::StateTagRole) {
219 return QString::fromStdString(s.primaryTag());
220 } else if (role == QmlAircraftInfo::StateDescriptionRole) {
221 return s.description;
222 } else if (role == QmlAircraftInfo::StateExplicitRole) {
223 if (s.primaryTag() == "auto")
224 return _explicitAutoState;
225 return true;
226 }
227
228 return {};
229}
230
231QHash<int, QByteArray> StatesModel::roleNames() const
232{
233 auto result = QAbstractListModel::roleNames();
234 result[Qt::DisplayRole] = "name";
235 result[QmlAircraftInfo::StateTagRole] = "tag";
236 result[QmlAircraftInfo::StateDescriptionRole] = "description";
237 return result;
238}
239
241{
242 size_t index;
243 if (!makeSafeIndex(row, index))
244 return {};
245
246 const auto& s = _data.at(index);
247 return s.description;
248}
249
250QString StatesModel::tagForState(int row) const
251{
252 size_t index;
253 if (!makeSafeIndex(row, index))
254 return {};
255
256 return QString::fromStdString(_data.at(index).primaryTag());
257}
258
260{
261 return _explicitAutoState;
262}
263
265{
266 return _data.empty();
267}
268
269bool StatesModel::hasState(QString st) const
270{
271 return indexForTag(st.toStdString()) != -1;
272}
273
274bool StatesModel::makeSafeIndex(int row, size_t& t) const
275{
276 if (row < 0) {
277 return false;
278 }
279 t = static_cast<size_t>(row);
280 return (t < _data.size());
281}
282
283
285
286void QmlAircraftInfo::Delegate::finishInstall(InstallRef aInstall, StatusCode aReason)
287{
288 Q_UNUSED(aReason)
289 if (aInstall->package() == p->packageRef()) {
290 p->_cachedProps.reset();
291 if (p->_statesModel) {
292 p->_statesModel->clear();
293 }
294 p->_statesChecked = false;
295 p->infoChanged();
296 }
297}
298
299
301
303 : QObject(parent)
304 , _delegate(new Delegate(this))
305{
306 qmlRegisterUncreatableType<StatesModel>("FlightGear.Launcher", 1, 0, "StatesModel", "no");
308 this, &QmlAircraftInfo::onFavouriteChanged);
309}
310
315
317{
318 if (_item) {
319 return QUrl::fromLocalFile(resolveItem()->path);
320 } else if (_package) {
321 return QUrl("package:" + QString::fromStdString(_package->qualifiedVariantId(_variant)));
322 }
323
324 return {};
325}
326
328{
329 if (_item) {
330 // for on-disk, we don't count the primary item
331 return static_cast<quint32>(_item->variants.size() + 1);
332 } else if (_package) {
333 // whereas for packaged aircraft we do
334 return static_cast<quint32>(_package->variants().size());
335 }
336
337 return 0;
338}
339
341{
342 if (_item) {
343 return resolveItem()->name();
344 } else if (_package) {
345 return QString::fromStdString(_package->nameForVariant(_variant));
346 }
347
348 return {};
349}
350
352{
353 if (_item) {
354 return resolveItem()->description();
355 } else if (_package) {
356 std::string longDesc = _package->getLocalisedProp("description", _variant);
357 return QString::fromStdString(longDesc).simplified();
358 }
359
360 return {};
361}
362
364{
365 SGPropertyNode_ptr structuredAuthors;
366 if (_item) {
367 validateLocalProps();
368 if (_cachedProps)
369 structuredAuthors = _cachedProps->getNode("sim/authors");
370
371 if (!structuredAuthors)
372 return resolveItem()->authors;
373 } else if (_package) {
374 if (_package->properties()->hasChild("authors")) {
375 structuredAuthors = _package->properties()->getChild("authors");
376 } else {
377 std::string authors = _package->getLocalisedProp("author", _variant);
378 return QString::fromStdString(authors);
379 }
380 }
381
382 if (structuredAuthors) {
383 // build formatted HTML based on authors data
384 QString html = "<ul>\n";
385 for (auto a : structuredAuthors->getChildren("author")) {
386 html += "<li>";
387 html += QString::fromStdString(a->getStringValue("name"));
388 if (a->hasChild("nick")) {
389 html += QStringLiteral(" '") + QString::fromStdString(a->getStringValue("nick")) + QStringLiteral("'");
390 }
391 if (a->hasChild("description")) {
392 html += QStringLiteral(" - <i>") + QString::fromStdString(a->getStringValue("description")) + QStringLiteral("</i>");
393 }
394 html += "</li>\n";
395 }
396 html += "<ul>\n";
397 return html;
398 }
399
400 return {};
401}
402
403QVariantList QmlAircraftInfo::ratings() const
404{
405 if (_item) {
406 QVariantList result;
407 auto actualItem = resolveItem();
408 for (int i=0; i<4; ++i) {
409 result << actualItem->ratings[i];
410 }
411 return result;
412 } else if (_package) {
413 SGPropertyNode* ratings = _package->properties()->getChild("rating");
414 if (!ratings) {
415 return {};
416 }
417
418 QVariantList result;
419 for (int i=0; i<4; ++i) {
420 result << ratings->getChild(i)->getIntValue();
421 }
422 return result;
423 }
424 return {};
425}
426
427QVariantList QmlAircraftInfo::previews() const
428{
429 if (_item) {
430 QVariantList result;
431 auto actualItem = resolveItem();
432 Q_FOREACH(QUrl u, actualItem->previews) {
433 result.append(u);
434 }
435 return result;
436 }
437
438 if (_package) {
439 const auto& previews = _package->previewsForVariant(_variant);
440 if (previews.empty()) {
441 return {};
442 }
443
444 QVariantList result;
445 // if we have an install, return file URLs, not remote (http) ones
446 auto ex = _package->existingInstall();
447 if (ex.valid()) {
448 for (auto p : previews) {
449 SGPath localPreviewPath = ex->path() / p.path;
450 if (!localPreviewPath.exists()) {
451 // this happens when the aircraft is being installed, for example
452 continue;
453 }
454 result.append(QUrl::fromLocalFile(QString::fromStdString(localPreviewPath.utf8Str())));
455 }
456 return result;
457 }
458
459 // return remote urls
460 for (auto p : previews) {
461 result.append(QUrl(QString::fromStdString(p.url)));
462 }
463
464 return result;
465 }
466
467 return {};
468}
469
471{
472 if (_item) {
473 return QUrl::fromLocalFile(resolveItem()->thumbnailPath);
474 } else if (_package) {
475 auto t = _package->thumbnailForVariant(_variant);
476 if (QFileInfo::exists(QString::fromStdString(t.path))) {
477 return QUrl::fromLocalFile(QString::fromStdString(t.path));
478 }
479 return QUrl(QString::fromStdString(t.url));
480 }
481
482 return {};
483}
484
486{
487 if (_item) {
488 return resolveItem()->path;
489 } else if (_package) {
490 auto install = _package->existingInstall();
491 if (install.valid()) {
492 return QString::fromStdString(install->primarySetPath().utf8Str());
493 }
494 }
495
496 return {};
497}
498
500{
501 if (_item) {
502 return resolveItem()->homepageUrl;
503 } else if (_package) {
504 const auto u = _package->getLocalisedProp("urls/home-page");
505 return QUrl(QString::fromStdString(u));
506 }
507
508 return {};
509}
510
512{
513 if (_item) {
514 return resolveItem()->supportUrl;
515 } else if (_package) {
516 const auto u = _package->getLocalisedProp("urls/support");
517 return QUrl(QString::fromStdString(u));
518 }
519
520 return {};
521}
522
524{
525 if (_item) {
526 return resolveItem()->wikipediaUrl;
527 } else if (_package) {
528 const auto u = _package->getLocalisedProp("urls/wikipedia");
529 return QUrl(QString::fromStdString(u));
530 }
531
532 return {};
533}
534
536{
537 if (_package) {
538 return QString::fromStdString(_package->variants()[_variant]);
539 }
540
541 return {};
542}
543
545{
546 if (_package) {
547 return _package->fileSizeBytes();
548 }
549
550 return 0;
551}
552
554{
555 return _downloadBytes;
556}
557
559{
560 if (_item) {
561 return _item->status(_variant);
562 } else if (_package) {
563 return packageAircraftStatus(_package);
564 }
565
567}
568
570{
571 if (_item) {
572 return resolveItem()->minFGVersion;
573 } else if (_package) {
574 const std::string v = _package->properties()->getStringValue("minimum-fg-version");
575 if (!v.empty()) {
576 return QString::fromStdString(v);
577 }
578 }
579
580 return {};
581}
582
583AircraftItemPtr QmlAircraftInfo::resolveItem() const
584{
585 if (_variant > 0) {
586 return _item->variants.at(_variant - 1);
587 }
588
589 return _item;
590}
591
592void QmlAircraftInfo::validateStates() const
593{
594 // after calling this, StatesModel must exist, but can be empty
595 if (!_statesModel) {
596 _statesModel = new StatesModel(const_cast<QmlAircraftInfo*>(this));
597 }
598
599 if (_statesChecked)
600 return;
601
602 validateLocalProps();
603 if (!_cachedProps)
604 return;
605
606 AircraftStateVec statesData;
607 readAircraftStates(_cachedProps, statesData);
608 _statesModel->initWithStates(statesData);
609 _statesChecked = true;
610}
611
612void QmlAircraftInfo::validateLocalProps() const
613{
614 if (!_cachedProps) {
615 // clear any previous states if we're reusing this object
616 if (_statesModel)
617 _statesModel->clear();
618
619 _statesChecked = false;
620
621 SGPath path = SGPath::fromUtf8(pathOnDisk().toStdString());
622 if (!path.exists())
623 return;
624 _cachedProps = new SGPropertyNode;
625 auto r = LocalAircraftCache::instance()->readAircraftProperties(path, _cachedProps);
627 _cachedProps.reset();
628
629 // give the AircraftScsn thread abit more time
630 QTimer::singleShot(2s, this, &QmlAircraftInfo::retryValidateLocalProps);
632 // we're good
633 } else {
634 // failure
635 _cachedProps.reset();
636 }
637 }
638}
639
641{
642 if (uri() == u)
643 return;
644
645 _item.clear();
646 _package.clear();
647 _cachedProps.clear();
648 _statesChecked = false;
649 if (_statesModel)
650 _statesModel->clear();
651
652 if (u.isLocalFile()) {
654 if (!_item) {
655 // scan still active or aircraft not found, let's bail out
656 // and rely on caller to try again
657 return;
658 }
659
660 int vindex = _item->indexOfVariant(u);
661 // we need to offset the variant index to allow for the different
662 // indexing schemes here (primary included) and in the cache (primary
663 // is not counted)
664 _variant = (vindex >= 0) ? static_cast<quint32>(vindex + 1) : 0U;
665 } else if (u.scheme() == "package") {
666 auto ident = u.path().toStdString();
667 try {
668 _package = globals->packageRoot()->getPackageById(ident);
669 if (_package) {
670 _variant = _package->indexOfVariant(ident);
671 }
672 } catch (sg_exception&) {
673 qWarning() << "couldn't find package/variant for " << u;
674 }
675 }
676
677 emit uriChanged();
678 emit infoChanged();
679 emit downloadChanged();
680 emit favouriteChanged();
681}
682
684{
685 if (!_item && !_package)
686 return;
687
688 if (variant >= numVariants()) {
689 qWarning() << Q_FUNC_INFO << uri() << "variant index out of range:" << variant;
690 return;
691 }
692
693 if (_variant == variant)
694 return;
695
696 _variant = variant;
697 _cachedProps.clear();
698 _statesChecked = false;
699 if (_statesModel)
700 _statesModel->clear();
701
702 emit infoChanged();
703 emit variantChanged(_variant);
704}
705
710
711void QmlAircraftInfo::onFavouriteChanged(QUrl u)
712{
713 if (u != uri())
714 return;
715
716 emit favouriteChanged();
717}
718
719void QmlAircraftInfo::retryValidateLocalProps()
720{
721 validateLocalProps();
722}
723
724QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p)
725{
726 if (p->hasTag("needs-maintenance")) {
728 }
729
730 if (!p->properties()->hasChild("minimum-fg-version")) {
732 }
733
734 const std::string minFGVersion = p->properties()->getStringValue("minimum-fg-version");
735 const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, minFGVersion, 2);
738}
739
741{
742 if (_item) {
744 }
745
746 if (_package) {
747 auto i = _package->existingInstall();
748 if (i.valid()) {
749 if (i->isDownloading()) {
751 }
752 if (i->isQueued()) {
754 }
755 if (i->hasUpdate()) {
757 }
758
760 } else {
762 }
763 }
764
766}
767
769{
770 return _package;
771}
772
774{
775 _downloadBytes = bytes;
776 emit downloadChanged();
777}
778
780{
781 QStringList result;
782 if (_item) {
783 result.append(_item->name());
784 Q_FOREACH(auto v, _item->variants) {
785 if (v->name().isEmpty()) {
786 qWarning() << Q_FUNC_INFO << "missing description for " << v->path;
787 }
788 result.append(v->name());
789 }
790 } else if (_package) {
791 for (quint32 vindex = 0; vindex < _package->variants().size(); ++vindex) {
792 if (_package->nameForVariant(vindex).empty()) {
793 qWarning() << Q_FUNC_INFO << "missing description for variant" << vindex;
794 }
795 result.append(QString::fromStdString(_package->nameForVariant(vindex)));
796 }
797 }
798 return result;
799}
800
802{
803 return _package != PackageRef();
804}
805
807{
808 validateStates();
809 return !_statesModel->isEmpty();
810}
811
813{
814 validateStates();
815 return _statesModel->hasState(name);
816}
817
819{
820 validateStates();
821 return _statesModel->hasExplicitAuto();
822}
823
825{
826 validateStates();
827 return _statesModel;
828}
829
831{
832 validateLocalProps();
833 if (!_cachedProps) {
834 return {};
835 }
836
837 if (_cachedProps->hasValue("aircraft/performance/cruise/mach")) {
838 return QuantityValue{Units::Mach, _cachedProps->getDoubleValue("aircraft/performance/cruise/mach")};
839 }
840
841 if (_cachedProps->hasValue("aircraft/performance/cruise/airpseed-knots")) {
842 return QuantityValue{Units::Knots, _cachedProps->getIntValue("aircraft/performance/cruise/airpseed-knots")};
843 }
844
845 return {};
846}
847
849{
850 validateLocalProps();
851 if (!_cachedProps) {
852 return {};
853 }
854
855 if (_cachedProps->hasValue("aircraft/performance/approach/airpseed-knots")) {
856 return QuantityValue{Units::Knots, _cachedProps->getIntValue("aircraft/performance/approach/airpseed-knots")};
857 }
858
859 return {};
860}
861
863{
864 validateLocalProps();
865 if (!_cachedProps) {
866 return {};
867 }
868
869 if (_cachedProps->hasValue("aircraft/performance/cruise/flight-level")) {
870 return QuantityValue{Units::FlightLevel, _cachedProps->getIntValue("aircraft/performance/cruise/flight-level")};
871 }
872
873 if (_cachedProps->hasValue("aircraft/performance/cruise/airpseed-knots")) {
874 return QuantityValue{Units::FeetMSL, _cachedProps->getIntValue("aircraft/performance/cruise/altitude-ft")};
875 }
876
877 return {};
878}
879
881{
882 validateLocalProps();
883 if (!_cachedProps) {
884 return {};
885 }
886
887 return QString::fromStdString(_cachedProps->getStringValue("aircraft/icao/type"));
888}
889
891{
892 Q_UNUSED(speed)
893 return true;
894}
895
897{
898 Q_UNUSED(speed)
899 return true;
900}
901
902bool QmlAircraftInfo::hasTag(QString tag) const
903{
904 if (_item) {
905 return resolveItem()->tags.contains(tag);
906 } else if (_package) {
907 const auto& tags = _package->tags();
908 auto it = tags.find(tag.toStdString());
909 return (it != tags.end());
910 }
911
912 return false;
913}
914
#define p(x)
QSharedPointer< AircraftItem > AircraftItemPtr
static bool readAircraftStates(const SGPropertyNode_ptr root, AircraftStateVec &result)
QString humanNameFromStateTag(const std::string &tag)
std::vector< AircraftStateInfo > AircraftStateVec
#define i(x)
void changed(QUrl u)
bool setFavourite(QUrl u, bool b)
static FavouriteAircraftData * instance()
ParseSetXMLResult readAircraftProperties(const SGPath &path, SGPropertyNode_ptr props)
readAircraftProperties - helper to parse a -set.xml, but with the correct path setup (root,...
@ Retry
aircraft scan in progress, try again later
static LocalAircraftCache * instance()
AircraftItemPtr findItemWithUri(QUrl aircraftUri) const
void catalogRefreshed(CatalogRef, StatusCode) override
void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total) override
void finishInstall(InstallRef aInstall, StatusCode aReason) override
void installStatusChanged(InstallRef aInstall, StatusCode aReason) override
Delegate(QmlAircraftInfo *info)
void startInstall(InstallRef aInstall) override
void finishUninstall(const PackageRef &pkg) override
QmlAircraftInfo(QObject *parent=nullptr)
QuantityValue cruiseAltitude
QVariantList ratings
static QVariant packageAircraftStatus(simgear::pkg::PackageRef p)
bool haveExplicitAutoState() const
void favouriteChanged()
static const int StateTagRole
QStringList variantNames
QVariantList previews
void setFavourite(bool favourite)
void setDownloadBytes(quint64 bytes)
QuantityValue approachSpeed
StatesModel * statesModel
static const int StateExplicitRole
Q_INVOKABLE bool isSpeedBelowLimits(QuantityValue speed) const
Q_INVOKABLE bool hasTag(QString tag) const
void setUri(QUrl uri)
void variantChanged(quint32 variant)
static const int StateDescriptionRole
void downloadChanged()
simgear::pkg::PackageRef packageRef() const
bool hasState(QString name) const
QuantityValue cruiseSpeed
void setVariant(quint32 variant)
Q_INVOKABLE bool isAltitudeBelowLimits(QuantityValue speed) const
QVariant data(const QModelIndex &index, int role) const override
bool hasState(QString st) const
Q_INVOKABLE int indexForTag(QString s) const
Q_INVOKABLE QString tagForState(int row) const
Q_INVOKABLE QString descriptionForState(int row) const
bool hasExplicitAuto() const
bool isEmpty() const
void initWithStates(const AircraftStateVec &states)
int rowCount(const QModelIndex &) const override
QHash< int, QByteArray > roleNames() const override
StatesModel(QObject *pr)
@ FlightLevel
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36