2017-10-09 20:53:26 +00:00
|
|
|
#include "QmlAircraftInfo.hxx"
|
|
|
|
|
|
|
|
#include <QVariant>
|
|
|
|
#include <QDebug>
|
2018-03-11 11:49:38 +00:00
|
|
|
#include <QQmlEngine>
|
2018-03-19 22:33:17 +00:00
|
|
|
#include <QQmlComponent>
|
2017-10-09 20:53:26 +00:00
|
|
|
|
|
|
|
#include <simgear/package/Install.hxx>
|
2017-10-13 15:48:24 +00:00
|
|
|
#include <simgear/package/Root.hxx>
|
|
|
|
#include <simgear/structure/exception.hxx>
|
2018-02-11 21:28:25 +00:00
|
|
|
#include <simgear/props/props_io.hxx>
|
2017-10-09 20:53:26 +00:00
|
|
|
|
|
|
|
#include <Include/version.h>
|
2017-10-13 15:48:24 +00:00
|
|
|
#include <Main/globals.hxx>
|
2017-10-09 20:53:26 +00:00
|
|
|
|
|
|
|
#include "LocalAircraftCache.hxx"
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
using namespace simgear::pkg;
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
const int QmlAircraftInfo::StateTagRole = Qt::UserRole + 1;
|
|
|
|
const int QmlAircraftInfo::StateDescriptionRole = Qt::UserRole + 2;
|
|
|
|
const int QmlAircraftInfo::StateExplicitRole = Qt::UserRole + 3;
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
class QmlAircraftInfo::Delegate : public simgear::pkg::Delegate
|
2017-10-09 20:53:26 +00:00
|
|
|
{
|
2017-10-13 15:48:24 +00:00
|
|
|
public:
|
|
|
|
Delegate(QmlAircraftInfo* info) :
|
|
|
|
p(info)
|
|
|
|
{
|
|
|
|
globals->packageRoot()->addDelegate(this);
|
|
|
|
}
|
|
|
|
|
2018-07-25 12:58:48 +00:00
|
|
|
~Delegate() override
|
2017-10-13 15:48:24 +00:00
|
|
|
{
|
|
|
|
globals->packageRoot()->removeDelegate(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void catalogRefreshed(CatalogRef, StatusCode) override
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void startInstall(InstallRef aInstall) override
|
|
|
|
{
|
|
|
|
if (aInstall->package() == p->packageRef()) {
|
|
|
|
p->setDownloadBytes(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total) override
|
|
|
|
{
|
2018-07-26 14:36:09 +00:00
|
|
|
Q_UNUSED(total)
|
2017-10-13 15:48:24 +00:00
|
|
|
if (aInstall->package() == p->packageRef()) {
|
|
|
|
p->setDownloadBytes(bytes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void finishInstall(InstallRef aInstall, StatusCode aReason) override
|
|
|
|
{
|
|
|
|
Q_UNUSED(aReason);
|
|
|
|
if (aInstall->package() == p->packageRef()) {
|
2018-07-26 14:36:09 +00:00
|
|
|
p->_cachedProps.reset();
|
2018-02-11 21:28:25 +00:00
|
|
|
p->checkForStates();
|
2017-10-13 15:48:24 +00:00
|
|
|
p->infoChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void installStatusChanged(InstallRef aInstall, StatusCode aReason) override
|
|
|
|
{
|
|
|
|
Q_UNUSED(aReason);
|
|
|
|
if (aInstall->package() == p->packageRef()) {
|
|
|
|
p->downloadChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void finishUninstall(const PackageRef& pkg) override
|
|
|
|
{
|
|
|
|
if (pkg == p->packageRef()) {
|
|
|
|
p->downloadChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2017-10-09 20:53:26 +00:00
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
QmlAircraftInfo* p;
|
|
|
|
};
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
struct StateInfo
|
|
|
|
{
|
2018-03-11 11:49:38 +00:00
|
|
|
string_list tags;
|
2018-02-11 21:28:25 +00:00
|
|
|
QString name; // human-readable name, or blank if we auto-generate this
|
|
|
|
QString description; // human-readable description
|
2018-03-11 11:49:38 +00:00
|
|
|
|
|
|
|
std::string primaryTag() const
|
|
|
|
{ return tags.front(); }
|
2018-02-11 21:28:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
using AircraftStateVec = std::vector<StateInfo>;
|
|
|
|
|
|
|
|
static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
|
|
|
|
{
|
|
|
|
SGPropertyNode_ptr root(new SGPropertyNode);
|
|
|
|
try {
|
|
|
|
readProperties(setXMLPath, root);
|
|
|
|
} catch (sg_exception&) {
|
|
|
|
// malformed include or XML, just bail
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!root->getNode("sim/state")) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto nodes = root->getNode("sim")->getChildren("state");
|
|
|
|
AircraftStateVec result;
|
|
|
|
result.reserve(nodes.size());
|
|
|
|
for (auto cn : nodes) {
|
2018-03-11 11:49:38 +00:00
|
|
|
string_list stateNames;
|
|
|
|
for (auto nameNode : cn->getChildren("name")) {
|
|
|
|
stateNames.push_back(nameNode->getStringValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stateNames.empty()) {
|
2018-04-09 16:13:15 +00:00
|
|
|
qWarning() << "state with no names defined, skipping" << QString::fromStdString(cn->getPath());
|
2018-03-11 11:49:38 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.push_back({stateNames,
|
2018-02-11 21:28:25 +00:00
|
|
|
QString::fromStdString(cn->getStringValue("readable-name")),
|
|
|
|
QString::fromStdString(cn->getStringValue("description"))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString humanNameFromStateTag(const std::string& tag)
|
|
|
|
{
|
|
|
|
if (tag == "approach") return QObject::tr("On approach");
|
|
|
|
if (tag == "take-off") return QObject::tr("Ready for Take-off");
|
|
|
|
if ((tag == "parking") || (tag == "cold-and-dark"))
|
|
|
|
return QObject::tr("Parked, cold & dark");
|
|
|
|
if (tag == "auto")
|
|
|
|
return QObject::tr("Automatic");
|
|
|
|
if (tag == "cruise")
|
|
|
|
return QObject::tr("Cruise");
|
2018-07-02 22:11:12 +00:00
|
|
|
if (tag == "taxi")
|
|
|
|
return QObject::tr("Ready to taxi");
|
2018-02-11 21:28:25 +00:00
|
|
|
|
|
|
|
qWarning() << Q_FUNC_INFO << "add for" << QString::fromStdString(tag);
|
|
|
|
// no mapping, let's use the tag directly
|
|
|
|
return QString::fromStdString(tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
class StatesModel : public QAbstractListModel
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
2018-03-11 11:49:38 +00:00
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
public:
|
2018-04-09 16:13:15 +00:00
|
|
|
StatesModel()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
StatesModel(const AircraftStateVec& states) :
|
|
|
|
_data(states)
|
|
|
|
{
|
2018-04-09 16:13:15 +00:00
|
|
|
// we use an empty model for aircraft with no states defined
|
|
|
|
if (states.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
// sort which places 'auto' item at the front if it exists
|
|
|
|
std::sort(_data.begin(), _data.end(), [](const StateInfo& a, const StateInfo& b) {
|
2018-03-11 11:49:38 +00:00
|
|
|
if (a.primaryTag() == "auto") return true;
|
|
|
|
if (b.primaryTag() == "auto") return false;
|
|
|
|
return a.primaryTag() < b.primaryTag();
|
2018-02-11 21:28:25 +00:00
|
|
|
});
|
|
|
|
|
2018-03-11 11:49:38 +00:00
|
|
|
if (_data.front().primaryTag() == "auto") {
|
2018-02-11 21:28:25 +00:00
|
|
|
// track if the aircraft supplied an 'auto' state, in which case
|
|
|
|
// we will not run our own selection logic
|
|
|
|
_explicitAutoState = true;
|
|
|
|
} else {
|
2018-03-11 11:49:38 +00:00
|
|
|
_data.insert(_data.begin(), {{"auto"}, {}, tr("Select state based on startup position.")});
|
2018-02-11 21:28:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-27 22:06:39 +00:00
|
|
|
Q_INVOKABLE int indexForTag(QString s) const
|
|
|
|
{
|
|
|
|
return indexForTag(s.toStdString());
|
|
|
|
}
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
int indexForTag(const std::string &tag) const
|
|
|
|
{
|
|
|
|
auto it = std::find_if(_data.begin(), _data.end(), [tag](const StateInfo& i) {
|
2018-03-11 11:49:38 +00:00
|
|
|
auto tagIt = std::find(i.tags.begin(), i.tags.end(), tag);
|
|
|
|
return (tagIt != i.tags.end());
|
2018-02-11 21:28:25 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
if (it == _data.end())
|
|
|
|
return -1;
|
|
|
|
|
2018-07-26 14:36:09 +00:00
|
|
|
return static_cast<int>(std::distance(_data.begin(), it));
|
2018-02-11 21:28:25 +00:00
|
|
|
}
|
|
|
|
|
2018-07-26 14:36:09 +00:00
|
|
|
int rowCount(const QModelIndex &) const override
|
2018-02-11 21:28:25 +00:00
|
|
|
{
|
2018-07-26 14:36:09 +00:00
|
|
|
return static_cast<int>(_data.size());
|
2018-02-11 21:28:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariant data(const QModelIndex &index, int role) const override
|
|
|
|
{
|
|
|
|
const StateInfo& s = _data.at(index.row());
|
|
|
|
if (role == Qt::DisplayRole) {
|
|
|
|
if (s.name.isEmpty()) {
|
2018-03-11 11:49:38 +00:00
|
|
|
return humanNameFromStateTag(s.primaryTag());
|
2018-02-11 21:28:25 +00:00
|
|
|
}
|
|
|
|
return s.name;
|
|
|
|
} else if (role == QmlAircraftInfo::StateTagRole) {
|
2018-03-11 11:49:38 +00:00
|
|
|
return QString::fromStdString(s.primaryTag());
|
2018-02-11 21:28:25 +00:00
|
|
|
} else if (role == QmlAircraftInfo::StateDescriptionRole) {
|
|
|
|
return s.description;
|
|
|
|
} else if (role == QmlAircraftInfo::StateExplicitRole) {
|
2018-03-11 11:49:38 +00:00
|
|
|
if (s.primaryTag() == "auto")
|
2018-02-11 21:28:25 +00:00
|
|
|
return _explicitAutoState;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QHash<int, QByteArray> roleNames() const override
|
|
|
|
{
|
|
|
|
auto result = QAbstractListModel::roleNames();
|
|
|
|
result[Qt::DisplayRole] = "name";
|
|
|
|
result[QmlAircraftInfo::StateTagRole] = "tag";
|
|
|
|
result[QmlAircraftInfo::StateDescriptionRole] = "description";
|
|
|
|
return result;
|
|
|
|
}
|
2018-03-11 11:49:38 +00:00
|
|
|
|
|
|
|
Q_INVOKABLE QString descriptionForState(int row) const
|
|
|
|
{
|
|
|
|
if ((row < 0) || (row >= _data.size()))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
const StateInfo& s = _data.at(row);
|
|
|
|
return s.description;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_INVOKABLE QString tagForState(int row) const
|
|
|
|
{
|
|
|
|
if ((row < 0) || (row >= _data.size()))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
return QString::fromStdString(_data.at(row).primaryTag());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hasExplicitAuto() const
|
|
|
|
{
|
|
|
|
return _explicitAutoState;
|
|
|
|
}
|
2018-04-09 16:13:15 +00:00
|
|
|
|
|
|
|
bool isEmpty() const
|
|
|
|
{
|
|
|
|
return _data.empty();
|
|
|
|
}
|
2018-05-31 23:44:12 +00:00
|
|
|
|
|
|
|
bool hasState(QString st) const
|
|
|
|
{
|
|
|
|
return indexForTag(st.toStdString()) != -1;
|
|
|
|
}
|
2018-02-11 21:28:25 +00:00
|
|
|
private:
|
|
|
|
AircraftStateVec _data;
|
|
|
|
bool _explicitAutoState = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
QmlAircraftInfo::QmlAircraftInfo(QObject *parent)
|
|
|
|
: QObject(parent)
|
|
|
|
, _delegate(new Delegate(this))
|
|
|
|
{
|
2018-03-11 11:49:38 +00:00
|
|
|
qmlRegisterUncreatableType<StatesModel>("FlightGear.Launcher", 1, 0, "StatesModel", "no");
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QmlAircraftInfo::~QmlAircraftInfo()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
QUrl QmlAircraftInfo::uri() const
|
2017-10-09 20:53:26 +00:00
|
|
|
{
|
2017-10-13 15:48:24 +00:00
|
|
|
if (_item) {
|
2017-12-08 22:03:09 +00:00
|
|
|
return QUrl::fromLocalFile(resolveItem()->path);
|
2017-10-13 15:48:24 +00:00
|
|
|
} else if (_package) {
|
|
|
|
return QUrl("package:" + QString::fromStdString(_package->qualifiedVariantId(_variant)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int QmlAircraftInfo::numVariants() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
2017-12-08 22:03:09 +00:00
|
|
|
// for on-disk, we don't count the primary item
|
|
|
|
return _item->variants.size() + 1;
|
2017-10-09 20:53:26 +00:00
|
|
|
} else if (_package) {
|
2017-12-08 22:03:09 +00:00
|
|
|
// whereas for packaged aircraft we do
|
2018-07-26 14:36:09 +00:00
|
|
|
return static_cast<int>(_package->variants().size());
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString QmlAircraftInfo::name() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return resolveItem()->description;
|
|
|
|
} else if (_package) {
|
|
|
|
return QString::fromStdString(_package->nameForVariant(_variant));
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QString QmlAircraftInfo::description() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return resolveItem()->longDescription;
|
|
|
|
} else if (_package) {
|
|
|
|
std::string longDesc = _package->getLocalisedProp("description", _variant);
|
|
|
|
return QString::fromStdString(longDesc).simplified();
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QString QmlAircraftInfo::authors() const
|
|
|
|
{
|
2018-07-25 12:58:48 +00:00
|
|
|
SGPropertyNode_ptr structuredAuthors;
|
2017-10-09 20:53:26 +00:00
|
|
|
if (_item) {
|
2018-07-26 14:36:09 +00:00
|
|
|
validateLocalProps();
|
|
|
|
if (_cachedProps)
|
|
|
|
structuredAuthors = _cachedProps->getNode("sim/authors");
|
|
|
|
|
2018-07-25 12:58:48 +00:00
|
|
|
if (!structuredAuthors)
|
|
|
|
return resolveItem()->authors;
|
2017-10-09 20:53:26 +00:00
|
|
|
} else if (_package) {
|
2018-07-25 12:58:48 +00:00
|
|
|
if (_package->properties()->hasChild("authors")) {
|
|
|
|
structuredAuthors = _package->properties()->getChild("authors");
|
|
|
|
} else {
|
|
|
|
std::string authors = _package->getLocalisedProp("author", _variant);
|
|
|
|
return QString::fromStdString(authors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (structuredAuthors) {
|
|
|
|
// build formatted HTML based on authors data
|
|
|
|
QString html = "<ul>\n";
|
|
|
|
for (auto a : structuredAuthors->getChildren("author")) {
|
|
|
|
html += "<li>";
|
|
|
|
html += a->getStringValue("name");
|
|
|
|
if (a->hasChild("nick")) {
|
|
|
|
html += QStringLiteral(" '") + QString::fromStdString(a->getStringValue("nick")) + QStringLiteral("'");
|
|
|
|
}
|
|
|
|
if (a->hasChild("description")) {
|
|
|
|
html += QStringLiteral(" - <i>") + QString::fromStdString(a->getStringValue("description")) + QStringLiteral("</i>");
|
|
|
|
}
|
|
|
|
html += "</li>\n";
|
|
|
|
}
|
|
|
|
html += "<ul>\n";
|
|
|
|
return html;
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantList QmlAircraftInfo::ratings() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
QVariantList result;
|
|
|
|
auto actualItem = resolveItem();
|
|
|
|
for (int i=0; i<4; ++i) {
|
|
|
|
result << actualItem->ratings[i];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
} else if (_package) {
|
|
|
|
SGPropertyNode* ratings = _package->properties()->getChild("rating");
|
|
|
|
if (!ratings) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantList result;
|
|
|
|
for (int i=0; i<4; ++i) {
|
|
|
|
result << ratings->getChild(i)->getIntValue();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
QVariantList QmlAircraftInfo::previews() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
QVariantList result;
|
2018-03-01 15:39:46 +00:00
|
|
|
auto actualItem = resolveItem();
|
|
|
|
Q_FOREACH(QUrl u, actualItem->previews) {
|
2017-10-13 15:48:24 +00:00
|
|
|
result.append(u);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_package) {
|
|
|
|
const auto& previews = _package->previewsForVariant(_variant);
|
|
|
|
if (previews.empty()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantList result;
|
|
|
|
// if we have an install, return file URLs, not remote (http) ones
|
|
|
|
auto ex = _package->existingInstall();
|
|
|
|
if (ex.valid()) {
|
|
|
|
for (auto p : previews) {
|
|
|
|
SGPath localPreviewPath = ex->path() / p.path;
|
|
|
|
if (!localPreviewPath.exists()) {
|
|
|
|
qWarning() << "missing local preview" << QString::fromStdString(localPreviewPath.utf8Str());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
result.append(QUrl::fromLocalFile(QString::fromStdString(localPreviewPath.utf8Str())));
|
|
|
|
}
|
2017-12-09 09:32:25 +00:00
|
|
|
return result;
|
2017-10-13 15:48:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// return remote urls
|
|
|
|
for (auto p : previews) {
|
|
|
|
result.append(QUrl(QString::fromStdString(p.url)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2017-10-09 20:53:26 +00:00
|
|
|
QUrl QmlAircraftInfo::thumbnail() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return QUrl::fromLocalFile(resolveItem()->thumbnailPath);
|
|
|
|
} else if (_package) {
|
|
|
|
auto t = _package->thumbnailForVariant(_variant);
|
|
|
|
if (QFileInfo::exists(QString::fromStdString(t.path))) {
|
|
|
|
return QUrl::fromLocalFile(QString::fromStdString(t.path));
|
|
|
|
}
|
|
|
|
return QUrl(QString::fromStdString(t.url));
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QString QmlAircraftInfo::pathOnDisk() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return resolveItem()->path;
|
|
|
|
} else if (_package) {
|
|
|
|
auto install = _package->existingInstall();
|
|
|
|
if (install.valid()) {
|
|
|
|
return QString::fromStdString(install->primarySetPath().utf8Str());
|
|
|
|
}
|
|
|
|
}
|
2018-07-26 11:03:21 +00:00
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QUrl QmlAircraftInfo::homePage() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return resolveItem()->homepageUrl;
|
|
|
|
} else if (_package) {
|
|
|
|
const auto u = _package->properties()->getStringValue("urls/home-page");
|
|
|
|
return QUrl(QString::fromStdString(u));
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QUrl QmlAircraftInfo::supportUrl() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return resolveItem()->supportUrl;
|
|
|
|
} else if (_package) {
|
|
|
|
const auto u = _package->properties()->getStringValue("urls/support");
|
|
|
|
return QUrl(QString::fromStdString(u));
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QUrl QmlAircraftInfo::wikipediaUrl() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return resolveItem()->wikipediaUrl;
|
|
|
|
} else if (_package) {
|
|
|
|
const auto u = _package->properties()->getStringValue("urls/wikipedia");
|
|
|
|
return QUrl(QString::fromStdString(u));
|
|
|
|
}
|
2017-10-09 20:53:26 +00:00
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QString QmlAircraftInfo::packageId() const
|
|
|
|
{
|
|
|
|
if (_package) {
|
|
|
|
return QString::fromStdString(_package->variants()[_variant]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
int QmlAircraftInfo::packageSize() const
|
|
|
|
{
|
|
|
|
if (_package) {
|
2018-07-26 14:36:09 +00:00
|
|
|
return static_cast<int>(_package->fileSizeBytes());
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int QmlAircraftInfo::downloadedBytes() const
|
|
|
|
{
|
2017-10-13 15:48:24 +00:00
|
|
|
return _downloadBytes;
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariant QmlAircraftInfo::status() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return _item->status(_variant);
|
|
|
|
} else if (_package) {
|
|
|
|
return packageAircraftStatus(_package);
|
|
|
|
}
|
|
|
|
|
2017-12-09 09:46:14 +00:00
|
|
|
return LocalAircraftCache::AircraftOk;
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString QmlAircraftInfo::minimumFGVersion() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return resolveItem()->minFGVersion;
|
|
|
|
} else if (_package) {
|
|
|
|
const std::string v = _package->properties()->getStringValue("minimum-fg-version");
|
|
|
|
if (!v.empty()) {
|
|
|
|
return QString::fromStdString(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
AircraftItemPtr QmlAircraftInfo::resolveItem() const
|
|
|
|
{
|
|
|
|
if (_variant > 0) {
|
|
|
|
return _item->variants.at(_variant - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _item;
|
|
|
|
}
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
void QmlAircraftInfo::checkForStates()
|
|
|
|
{
|
|
|
|
QString path = pathOnDisk();
|
|
|
|
if (path.isEmpty()) {
|
2018-04-09 16:13:15 +00:00
|
|
|
_statesModel.reset(new StatesModel);
|
2018-02-11 21:28:25 +00:00
|
|
|
emit infoChanged();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto states = readAircraftStates(SGPath::fromUtf8(path.toUtf8().toStdString()));
|
|
|
|
if (states.empty()) {
|
2018-04-09 16:13:15 +00:00
|
|
|
_statesModel.reset(new StatesModel);
|
2018-02-11 21:28:25 +00:00
|
|
|
emit infoChanged();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_statesModel.reset(new StatesModel(states));
|
|
|
|
emit infoChanged();
|
|
|
|
}
|
|
|
|
|
2018-07-26 14:36:09 +00:00
|
|
|
void QmlAircraftInfo::validateLocalProps() const
|
|
|
|
{
|
|
|
|
if (!_cachedProps) {
|
|
|
|
SGPath path;
|
|
|
|
if (_item) {
|
|
|
|
path = resolveItem()->path.toUtf8().toStdString();
|
|
|
|
} else if (_package) {
|
|
|
|
auto install = _package->existingInstall();
|
|
|
|
if (!install.valid())
|
|
|
|
return;
|
|
|
|
|
|
|
|
path = install->path();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!path.exists())
|
|
|
|
return;
|
|
|
|
_cachedProps = new SGPropertyNode;
|
|
|
|
try {
|
|
|
|
readProperties(path, _cachedProps);
|
|
|
|
} catch (sg_exception&) {
|
|
|
|
_cachedProps.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
void QmlAircraftInfo::setUri(QUrl u)
|
2017-10-09 20:53:26 +00:00
|
|
|
{
|
2017-10-13 15:48:24 +00:00
|
|
|
if (uri() == u)
|
2017-10-09 20:53:26 +00:00
|
|
|
return;
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
_item.clear();
|
|
|
|
_package.clear();
|
2018-04-09 16:13:15 +00:00
|
|
|
_statesModel.reset(new StatesModel);
|
2018-07-26 14:36:09 +00:00
|
|
|
_cachedProps.clear();
|
2017-10-09 20:53:26 +00:00
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
if (u.isLocalFile()) {
|
|
|
|
_item = LocalAircraftCache::instance()->findItemWithUri(u);
|
2018-02-11 21:28:25 +00:00
|
|
|
if (!_item) {
|
|
|
|
// scan still active or aircraft not found, let's bail out
|
|
|
|
// and rely on caller to try again
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-08 22:03:09 +00:00
|
|
|
int vindex = _item->indexOfVariant(u);
|
|
|
|
// we need to offset the variant index to allow for the different
|
|
|
|
// indexing schemes here (primary included) and in the cache (primary
|
|
|
|
// is not counted)
|
|
|
|
_variant = (vindex >= 0) ? vindex + 1 : 0;
|
2017-10-13 15:48:24 +00:00
|
|
|
} else if (u.scheme() == "package") {
|
|
|
|
auto ident = u.path().toStdString();
|
|
|
|
try {
|
|
|
|
_package = globals->packageRoot()->getPackageById(ident);
|
2018-02-27 11:33:31 +00:00
|
|
|
if (_package) {
|
|
|
|
_variant = _package->indexOfVariant(ident);
|
|
|
|
}
|
2017-10-13 15:48:24 +00:00
|
|
|
} catch (sg_exception&) {
|
|
|
|
qWarning() << "couldn't find package/variant for " << u;
|
|
|
|
}
|
|
|
|
}
|
2017-10-09 20:53:26 +00:00
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
checkForStates();
|
|
|
|
|
2017-10-09 20:53:26 +00:00
|
|
|
emit uriChanged();
|
|
|
|
emit infoChanged();
|
2017-10-13 15:48:24 +00:00
|
|
|
emit downloadChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QmlAircraftInfo::setVariant(int variant)
|
|
|
|
{
|
2017-12-08 22:03:09 +00:00
|
|
|
if (!_item && !_package)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ((variant < 0) || (variant >= numVariants())) {
|
|
|
|
qWarning() << Q_FUNC_INFO << uri() << "variant index out of range:" << variant;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
if (_variant == variant)
|
|
|
|
return;
|
|
|
|
|
|
|
|
_variant = variant;
|
2018-07-26 14:36:09 +00:00
|
|
|
_cachedProps.clear();
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
checkForStates();
|
|
|
|
|
2017-10-13 15:48:24 +00:00
|
|
|
emit infoChanged();
|
|
|
|
emit variantChanged(_variant);
|
2017-10-09 20:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p)
|
|
|
|
{
|
|
|
|
if (p->hasTag("needs-maintenance")) {
|
|
|
|
return LocalAircraftCache::AircraftUnmaintained;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!p->properties()->hasChild("minimum-fg-version")) {
|
|
|
|
return LocalAircraftCache::AircraftOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string minFGVersion = p->properties()->getStringValue("minimum-fg-version");
|
|
|
|
const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, minFGVersion, 2);
|
|
|
|
return (c < 0) ? LocalAircraftCache::AircraftNeedsNewerSimulator :
|
|
|
|
LocalAircraftCache::AircraftOk;
|
|
|
|
}
|
2017-10-13 15:48:24 +00:00
|
|
|
|
|
|
|
QVariant QmlAircraftInfo::installStatus() const
|
|
|
|
{
|
|
|
|
if (_item) {
|
|
|
|
return LocalAircraftCache::PackageInstalled;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_package) {
|
|
|
|
auto i = _package->existingInstall();
|
|
|
|
if (i.valid()) {
|
|
|
|
if (i->isDownloading()) {
|
|
|
|
return LocalAircraftCache::PackageDownloading;
|
|
|
|
}
|
|
|
|
if (i->isQueued()) {
|
|
|
|
return LocalAircraftCache::PackageQueued;
|
|
|
|
}
|
|
|
|
if (i->hasUpdate()) {
|
|
|
|
return LocalAircraftCache::PackageUpdateAvailable;
|
|
|
|
}
|
|
|
|
|
|
|
|
return LocalAircraftCache::PackageInstalled;
|
|
|
|
} else {
|
|
|
|
return LocalAircraftCache::PackageNotInstalled;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-09 09:46:14 +00:00
|
|
|
return LocalAircraftCache::NotPackaged;
|
2017-10-13 15:48:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PackageRef QmlAircraftInfo::packageRef() const
|
|
|
|
{
|
|
|
|
return _package;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QmlAircraftInfo::setDownloadBytes(int bytes)
|
|
|
|
{
|
|
|
|
_downloadBytes = bytes;
|
2018-02-11 21:28:25 +00:00
|
|
|
emit downloadChanged();
|
2017-10-13 15:48:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QStringList QmlAircraftInfo::variantNames() const
|
|
|
|
{
|
|
|
|
QStringList result;
|
|
|
|
if (_item) {
|
2017-12-08 22:03:09 +00:00
|
|
|
result.append(_item->description);
|
2017-10-13 15:48:24 +00:00
|
|
|
Q_FOREACH(auto v, _item->variants) {
|
|
|
|
if (v->description.isEmpty()) {
|
|
|
|
qWarning() << Q_FUNC_INFO << "missing description for " << v->path;
|
|
|
|
}
|
|
|
|
result.append(v->description);
|
|
|
|
}
|
|
|
|
} else if (_package) {
|
|
|
|
for (int vindex = 0; vindex < _package->variants().size(); ++vindex) {
|
|
|
|
if (_package->nameForVariant(vindex).empty()) {
|
|
|
|
qWarning() << Q_FUNC_INFO << "missing description for variant" << vindex;
|
|
|
|
}
|
|
|
|
result.append(QString::fromStdString(_package->nameForVariant(vindex)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2017-12-08 22:03:09 +00:00
|
|
|
|
|
|
|
bool QmlAircraftInfo::isPackaged() const
|
|
|
|
{
|
|
|
|
return _package != PackageRef();
|
|
|
|
}
|
2018-02-11 21:28:25 +00:00
|
|
|
|
2018-04-09 16:13:15 +00:00
|
|
|
bool QmlAircraftInfo::hasStates() const
|
2018-02-11 21:28:25 +00:00
|
|
|
{
|
2018-04-09 16:13:15 +00:00
|
|
|
return !_statesModel->isEmpty();
|
|
|
|
}
|
2018-02-11 21:28:25 +00:00
|
|
|
|
2018-05-31 23:44:12 +00:00
|
|
|
bool QmlAircraftInfo::hasState(QString name) const
|
|
|
|
{
|
|
|
|
return _statesModel->hasState(name);
|
|
|
|
}
|
|
|
|
|
2018-06-27 22:06:39 +00:00
|
|
|
bool QmlAircraftInfo::haveExplicitAutoState() const
|
|
|
|
{
|
|
|
|
return _statesModel->hasExplicitAuto();
|
|
|
|
}
|
|
|
|
|
2018-04-09 16:13:15 +00:00
|
|
|
StatesModel *QmlAircraftInfo::statesModel()
|
|
|
|
{
|
2018-02-11 21:28:25 +00:00
|
|
|
return _statesModel.data();
|
|
|
|
}
|
|
|
|
|
2018-07-26 14:36:09 +00:00
|
|
|
QuantityValue QmlAircraftInfo::cruiseSpeed() const
|
|
|
|
{
|
|
|
|
validateLocalProps();
|
|
|
|
if (!_cachedProps) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_cachedProps->hasValue("aircraft/performance/cruise/mach")) {
|
|
|
|
return QuantityValue{Units::Mach, _cachedProps->getDoubleValue("aircraft/performance/cruise/mach")};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_cachedProps->hasValue("aircraft/performance/cruise/airpseed-knots")) {
|
|
|
|
return QuantityValue{Units::Knots, _cachedProps->getIntValue("aircraft/performance/cruise/airpseed-knots")};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QuantityValue QmlAircraftInfo::approachSpeed() const
|
|
|
|
{
|
|
|
|
validateLocalProps();
|
|
|
|
if (!_cachedProps) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_cachedProps->hasValue("aircraft/performance/approach/airpseed-knots")) {
|
|
|
|
return QuantityValue{Units::Knots, _cachedProps->getIntValue("aircraft/performance/approach/airpseed-knots")};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QuantityValue QmlAircraftInfo::cruiseAltitude() const
|
|
|
|
{
|
|
|
|
validateLocalProps();
|
|
|
|
if (!_cachedProps) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_cachedProps->hasValue("aircraft/performance/cruise/flight-level")) {
|
|
|
|
return QuantityValue{Units::FlightLevel, _cachedProps->getIntValue("aircraft/performance/cruise/flight-level")};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_cachedProps->hasValue("aircraft/performance/cruise/airpseed-knots")) {
|
|
|
|
return QuantityValue{Units::FeetMSL, _cachedProps->getIntValue("aircraft/performance/cruise/altitude-ft")};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
QString QmlAircraftInfo::icaoType() const
|
|
|
|
{
|
|
|
|
validateLocalProps();
|
|
|
|
if (!_cachedProps) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return QString::fromStdString(_cachedProps->getStringValue("aircraft/icao/type"));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QmlAircraftInfo::isSpeedBelowLimits(QuantityValue speed) const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QmlAircraftInfo::isAltitudeBelowLimits(QuantityValue speed) const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-11 21:28:25 +00:00
|
|
|
#include "QmlAircraftInfo.moc"
|
|
|
|
|