8#include <QQmlComponent>
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>
22using namespace std::chrono_literals;
34 globals->packageRoot()->addDelegate(
this);
39 globals->packageRoot()->removeDelegate(
this);
43 void finishInstall(InstallRef aInstall, StatusCode aReason)
override;
51 if (aInstall->package() == p->packageRef()) {
52 p->setDownloadBytes(0);
56 void installProgress(InstallRef aInstall,
unsigned int bytes,
unsigned int total)
override
59 if (aInstall->package() == p->packageRef()) {
60 p->setDownloadBytes(bytes);
67 if (aInstall->package() == p->packageRef()) {
74 if (
pkg == p->packageRef()) {
90 if (!root->getNode(
"sim/state")) {
94 auto nodes = root->getNode(
"sim")->getChildren(
"state");
95 result.reserve(nodes.size());
96 for (
auto cn : nodes) {
98 for (
auto nameNode : cn->getChildren(
"name")) {
99 stateNames.push_back(nameNode->getStringValue());
102 if (stateNames.empty()) {
103 qWarning() <<
"state with no names defined, skipping" << QString::fromStdString(cn->getPath());
107 result.push_back({stateNames,
108 QString::fromStdString(cn->getStringValue(
"readable-name")),
109 QString::fromStdString(cn->getStringValue(
"description"))
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");
124 return QObject::tr(
"Automatic");
126 return QObject::tr(
"Cruise");
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");
134 qWarning() << Q_FUNC_INFO <<
"add translation / string for" << QString::fromStdString(tag);
136 return QString::fromStdString(tag);
150 _explicitAutoState =
false;
158 _explicitAutoState =
false;
161 if (states.empty()) {
168 if (a.primaryTag() ==
"auto") return true;
169 if (b.primaryTag() ==
"auto") return false;
170 return a.primaryTag() < b.primaryTag();
173 if (_data.front().primaryTag() ==
"auto") {
176 _explicitAutoState =
true;
178 _data.insert(_data.begin(), {{
"auto"}, {}, tr(
"Select state based on startup position.")});
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());
196 if (it == _data.end())
199 return static_cast<int>(std::distance(_data.begin(), it));
204 return static_cast<int>(_data.size());
210 if (!makeSafeIndex(index.row(),
i))
212 const auto& s = _data.at(
i);
213 if (role == Qt::DisplayRole) {
214 if (s.name.isEmpty()) {
219 return QString::fromStdString(s.primaryTag());
221 return s.description;
223 if (s.primaryTag() ==
"auto")
224 return _explicitAutoState;
233 auto result = QAbstractListModel::roleNames();
234 result[Qt::DisplayRole] =
"name";
243 if (!makeSafeIndex(row, index))
246 const auto& s = _data.at(index);
247 return s.description;
253 if (!makeSafeIndex(row, index))
256 return QString::fromStdString(_data.at(index).primaryTag());
261 return _explicitAutoState;
266 return _data.empty();
274bool StatesModel::makeSafeIndex(
int row,
size_t& t)
const
279 t =
static_cast<size_t>(row);
280 return (t < _data.size());
289 if (aInstall->package() == p->packageRef()) {
290 p->_cachedProps.reset();
291 if (p->_statesModel) {
292 p->_statesModel->clear();
294 p->_statesChecked =
false;
306 qmlRegisterUncreatableType<StatesModel>(
"FlightGear.Launcher", 1, 0,
"StatesModel",
"no");
308 this, &QmlAircraftInfo::onFavouriteChanged);
319 return QUrl::fromLocalFile(resolveItem()->path);
320 }
else if (_package) {
321 return QUrl(
"package:" + QString::fromStdString(_package->qualifiedVariantId(_variant)));
331 return static_cast<quint32
>(_item->variants.size() + 1);
332 }
else if (_package) {
334 return static_cast<quint32
>(_package->variants().size());
343 return resolveItem()->name();
344 }
else if (_package) {
345 return QString::fromStdString(_package->nameForVariant(_variant));
354 return resolveItem()->description();
355 }
else if (_package) {
356 std::string longDesc = _package->getLocalisedProp(
"description", _variant);
357 return QString::fromStdString(longDesc).simplified();
365 SGPropertyNode_ptr structuredAuthors;
367 validateLocalProps();
369 structuredAuthors = _cachedProps->getNode(
"sim/authors");
371 if (!structuredAuthors)
372 return resolveItem()->authors;
373 }
else if (_package) {
374 if (_package->properties()->hasChild(
"authors")) {
375 structuredAuthors = _package->properties()->getChild(
"authors");
377 std::string
authors = _package->getLocalisedProp(
"author", _variant);
378 return QString::fromStdString(
authors);
382 if (structuredAuthors) {
384 QString html =
"<ul>\n";
385 for (
auto a : structuredAuthors->getChildren(
"author")) {
387 html += QString::fromStdString(a->getStringValue(
"name"));
388 if (a->hasChild(
"nick")) {
389 html += QStringLiteral(
" '") + QString::fromStdString(a->getStringValue(
"nick")) + QStringLiteral(
"'");
391 if (a->hasChild(
"description")) {
392 html += QStringLiteral(
" - <i>") + QString::fromStdString(a->getStringValue(
"description")) + QStringLiteral(
"</i>");
407 auto actualItem = resolveItem();
408 for (
int i=0;
i<4; ++
i) {
409 result << actualItem->ratings[
i];
412 }
else if (_package) {
413 SGPropertyNode*
ratings = _package->properties()->getChild(
"rating");
419 for (
int i=0;
i<4; ++
i) {
420 result <<
ratings->getChild(
i)->getIntValue();
431 auto actualItem = resolveItem();
432 Q_FOREACH(QUrl u, actualItem->previews) {
439 const auto&
previews = _package->previewsForVariant(_variant);
446 auto ex = _package->existingInstall();
449 SGPath localPreviewPath = ex->path() /
p.path;
450 if (!localPreviewPath.exists()) {
454 result.append(QUrl::fromLocalFile(QString::fromStdString(localPreviewPath.utf8Str())));
461 result.append(QUrl(QString::fromStdString(
p.url)));
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));
479 return QUrl(QString::fromStdString(t.url));
488 return resolveItem()->path;
489 }
else if (_package) {
490 auto install = _package->existingInstall();
491 if (install.valid()) {
492 return QString::fromStdString(install->primarySetPath().utf8Str());
502 return resolveItem()->homepageUrl;
503 }
else if (_package) {
504 const auto u = _package->getLocalisedProp(
"urls/home-page");
505 return QUrl(QString::fromStdString(u));
514 return resolveItem()->supportUrl;
515 }
else if (_package) {
516 const auto u = _package->getLocalisedProp(
"urls/support");
517 return QUrl(QString::fromStdString(u));
526 return resolveItem()->wikipediaUrl;
527 }
else if (_package) {
528 const auto u = _package->getLocalisedProp(
"urls/wikipedia");
529 return QUrl(QString::fromStdString(u));
538 return QString::fromStdString(_package->variants()[_variant]);
547 return _package->fileSizeBytes();
555 return _downloadBytes;
561 return _item->status(_variant);
562 }
else if (_package) {
572 return resolveItem()->minFGVersion;
573 }
else if (_package) {
574 const std::string v = _package->properties()->getStringValue(
"minimum-fg-version");
576 return QString::fromStdString(v);
586 return _item->variants.at(_variant - 1);
592void QmlAircraftInfo::validateStates()
const
602 validateLocalProps();
608 _statesModel->initWithStates(statesData);
609 _statesChecked =
true;
612void QmlAircraftInfo::validateLocalProps()
const
617 _statesModel->clear();
619 _statesChecked =
false;
621 SGPath path = SGPath::fromUtf8(
pathOnDisk().toStdString());
624 _cachedProps =
new SGPropertyNode;
627 _cachedProps.reset();
630 QTimer::singleShot(2s,
this, &QmlAircraftInfo::retryValidateLocalProps);
635 _cachedProps.reset();
647 _cachedProps.clear();
648 _statesChecked =
false;
650 _statesModel->clear();
652 if (u.isLocalFile()) {
660 int vindex = _item->indexOfVariant(u);
664 _variant = (vindex >= 0) ?
static_cast<quint32
>(vindex + 1) : 0U;
665 }
else if (u.scheme() ==
"package") {
666 auto ident = u.path().toStdString();
668 _package =
globals->packageRoot()->getPackageById(ident);
670 _variant = _package->indexOfVariant(ident);
672 }
catch (sg_exception&) {
673 qWarning() <<
"couldn't find package/variant for " << u;
685 if (!_item && !_package)
689 qWarning() << Q_FUNC_INFO <<
uri() <<
"variant index out of range:" <<
variant;
697 _cachedProps.clear();
698 _statesChecked =
false;
700 _statesModel->clear();
711void QmlAircraftInfo::onFavouriteChanged(QUrl u)
719void QmlAircraftInfo::retryValidateLocalProps()
721 validateLocalProps();
726 if (
p->hasTag(
"needs-maintenance")) {
730 if (!
p->properties()->hasChild(
"minimum-fg-version")) {
734 const std::string minFGVersion =
p->properties()->getStringValue(
"minimum-fg-version");
735 const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, minFGVersion, 2);
747 auto i = _package->existingInstall();
749 if (
i->isDownloading()) {
755 if (
i->hasUpdate()) {
775 _downloadBytes = bytes;
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;
788 result.append(v->name());
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;
795 result.append(QString::fromStdString(_package->nameForVariant(vindex)));
803 return _package != PackageRef();
809 return !_statesModel->isEmpty();
815 return _statesModel->hasState(
name);
821 return _statesModel->hasExplicitAuto();
832 validateLocalProps();
837 if (_cachedProps->hasValue(
"aircraft/performance/cruise/mach")) {
841 if (_cachedProps->hasValue(
"aircraft/performance/cruise/airpseed-knots")) {
850 validateLocalProps();
855 if (_cachedProps->hasValue(
"aircraft/performance/approach/airpseed-knots")) {
864 validateLocalProps();
869 if (_cachedProps->hasValue(
"aircraft/performance/cruise/flight-level")) {
873 if (_cachedProps->hasValue(
"aircraft/performance/cruise/airpseed-knots")) {
882 validateLocalProps();
887 return QString::fromStdString(_cachedProps->getStringValue(
"aircraft/icao/type"));
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());
QSharedPointer< AircraftItem > AircraftItemPtr
static bool readAircraftStates(const SGPropertyNode_ptr root, AircraftStateVec &result)
QString humanNameFromStateTag(const std::string &tag)
std::vector< AircraftStateInfo > AircraftStateVec
bool isFavourite(QUrl u) const
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
@ AircraftNeedsNewerSimulator
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
static QVariant packageAircraftStatus(simgear::pkg::PackageRef p)
bool haveExplicitAutoState() const
static const int StateTagRole
virtual ~QmlAircraftInfo()
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 variantChanged(quint32 variant)
static const int StateDescriptionRole
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
void initWithStates(const AircraftStateVec &states)
int rowCount(const QModelIndex &) const override
QHash< int, QByteArray > roleNames() const override
std::vector< std::string > string_list