Launcher: overhaul local -set.xml parsing
Fix the include context when -set.xml are parsed by the launcher to extract states and other data. Ensure aircraft paths are available and handled consistently with normal -set.xml parsing. Without this, many -set.xmls fail to parse. Sentry-Id: FLIGHTGEAR-4H Sentry-Id: FLIGHTGEAR-3B Sentry-Id: FLIGHTGEAR-3R Sentry-Id: FLIGHTGEAR-3Q Sentry-Id: FLIGHTGEAR-3F Sentry-Id: FLIGHTGEAR-2R Sentry-Id: FLIGHTGEAR-65 Sentry-Id: FLIGHTGEAR-42
This commit is contained in:
parent
2b54078023
commit
582d539a1a
4 changed files with 238 additions and 98 deletions
|
@ -277,6 +277,11 @@ QVariant AircraftItem::status(int variant)
|
|||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static std::unique_ptr<LocalAircraftCache> static_cacheInstance;
|
||||
|
||||
|
||||
// ensure references to Aircraft/foo and <my-aircraft-dir>/foo are resolved. This happens when
|
||||
// aircraft reference a path (probably to themselves) in their -set.xml
|
||||
class ScanDirProvider : public simgear::ResourceProvider
|
||||
|
@ -322,6 +327,34 @@ private:
|
|||
SGPath _currentAircraftPath;
|
||||
};
|
||||
|
||||
class OtherAircraftDirsProvider : public simgear::ResourceProvider
|
||||
{
|
||||
public:
|
||||
OtherAircraftDirsProvider() : simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_NORMAL) {}
|
||||
|
||||
~OtherAircraftDirsProvider() = default;
|
||||
|
||||
SGPath resolve(const std::string& aResource, SGPath& aContext) const override
|
||||
{
|
||||
if (aResource.find("Aircraft/") != 0) {
|
||||
return SGPath{}; // not an aircraft path
|
||||
}
|
||||
|
||||
const std::string res(aResource, 9); // resource path with 'Aircraft/' removed
|
||||
|
||||
Q_UNUSED(aContext)
|
||||
QStringList paths = LocalAircraftCache::instance()->paths();
|
||||
Q_FOREACH(auto p, paths) {
|
||||
const auto sp = SGPath::fromUtf8(p.toUtf8().toStdString()) / res;
|
||||
// qWarning() << "OADP: trying:" << QString::fromStdString(sp.utf8Str());
|
||||
if (sp.exists())
|
||||
return sp;
|
||||
}
|
||||
|
||||
return SGPath{};
|
||||
}
|
||||
};
|
||||
|
||||
class AircraftScanThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -511,7 +544,17 @@ private:
|
|||
std::unique_ptr<ScanDirProvider> m_currentScanDir;
|
||||
};
|
||||
|
||||
static std::unique_ptr<LocalAircraftCache> static_cacheInstance;
|
||||
} // of anonymous namespace
|
||||
|
||||
|
||||
class LocalAircraftCache::AircraftCachePrivate
|
||||
{
|
||||
public:
|
||||
QStringList m_paths;
|
||||
std::unique_ptr<AircraftScanThread> m_scanThread;
|
||||
QVector<AircraftItemPtr> m_items;
|
||||
std::unique_ptr<OtherAircraftDirsProvider> m_otherDirsProvider;
|
||||
};
|
||||
|
||||
LocalAircraftCache* LocalAircraftCache::instance()
|
||||
{
|
||||
|
@ -522,33 +565,48 @@ LocalAircraftCache* LocalAircraftCache::instance()
|
|||
return static_cacheInstance.get();
|
||||
}
|
||||
|
||||
LocalAircraftCache::LocalAircraftCache()
|
||||
LocalAircraftCache::LocalAircraftCache() :
|
||||
d(new AircraftCachePrivate)
|
||||
{
|
||||
|
||||
d->m_otherDirsProvider.reset(new OtherAircraftDirsProvider);
|
||||
auto rm = simgear::ResourceManager::instance();
|
||||
rm->addProvider(d->m_otherDirsProvider.get());
|
||||
}
|
||||
|
||||
LocalAircraftCache::~LocalAircraftCache()
|
||||
{
|
||||
abandonCurrentScan();
|
||||
if (simgear::ResourceManager::haveInstance()) {
|
||||
simgear::ResourceManager::instance()->removeProvider(d->m_otherDirsProvider.get());
|
||||
} else {
|
||||
// resource manager will already have destroyed the provider. Awkward
|
||||
// ownership model :(
|
||||
d->m_otherDirsProvider.release();
|
||||
}
|
||||
}
|
||||
|
||||
void LocalAircraftCache::setPaths(QStringList paths)
|
||||
{
|
||||
if (paths == m_paths) {
|
||||
if (paths == d->m_paths) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_items.clear();
|
||||
d->m_items.clear();
|
||||
emit cleared();
|
||||
m_paths = paths;
|
||||
d->m_paths = paths;
|
||||
}
|
||||
|
||||
QStringList LocalAircraftCache::paths() const
|
||||
{
|
||||
return d->m_paths;
|
||||
}
|
||||
|
||||
void LocalAircraftCache::scanDirs()
|
||||
{
|
||||
abandonCurrentScan();
|
||||
m_items.clear();
|
||||
d->m_items.clear();
|
||||
|
||||
QStringList dirs = m_paths;
|
||||
QStringList dirs = d->m_paths;
|
||||
|
||||
for (SGPath ap : globals->get_aircraft_paths()) {
|
||||
dirs << QString::fromStdString(ap.utf8Str());
|
||||
|
@ -557,40 +615,40 @@ void LocalAircraftCache::scanDirs()
|
|||
SGPath rootAircraft = globals->get_fg_root() / "Aircraft";
|
||||
dirs << QString::fromStdString(rootAircraft.utf8Str());
|
||||
|
||||
m_scanThread.reset(new AircraftScanThread(dirs));
|
||||
connect(m_scanThread.get(), &AircraftScanThread::finished, this,
|
||||
d->m_scanThread.reset(new AircraftScanThread(dirs));
|
||||
connect(d->m_scanThread.get(), &AircraftScanThread::finished, this,
|
||||
&LocalAircraftCache::onScanFinished);
|
||||
// force a queued connection here since we the scan thread object still
|
||||
// belongs to the same thread as us, and hence this would otherwise be
|
||||
// a direct connection
|
||||
connect(m_scanThread.get(), &AircraftScanThread::addedItems,
|
||||
connect(d->m_scanThread.get(), &AircraftScanThread::addedItems,
|
||||
this, &LocalAircraftCache::onScanResults,
|
||||
Qt::QueuedConnection);
|
||||
m_scanThread->start();
|
||||
d->m_scanThread->start();
|
||||
|
||||
emit scanStarted();
|
||||
}
|
||||
|
||||
int LocalAircraftCache::itemCount() const
|
||||
{
|
||||
return m_items.size();
|
||||
return d->m_items.size();
|
||||
}
|
||||
|
||||
QVector<AircraftItemPtr> LocalAircraftCache::allItems() const
|
||||
{
|
||||
return m_items;
|
||||
return d->m_items;
|
||||
}
|
||||
|
||||
AircraftItemPtr LocalAircraftCache::itemAt(int index) const
|
||||
{
|
||||
return m_items.at(index);
|
||||
return d->m_items.at(index);
|
||||
}
|
||||
|
||||
int LocalAircraftCache::findIndexWithUri(QUrl aircraftUri) const
|
||||
{
|
||||
QString path = aircraftUri.toLocalFile();
|
||||
for (int row=0; row < m_items.size(); ++row) {
|
||||
const AircraftItemPtr item(m_items.at(row));
|
||||
for (int row=0; row < d->m_items.size(); ++row) {
|
||||
const AircraftItemPtr item(d->m_items.at(row));
|
||||
if (item->path == path) {
|
||||
return row;
|
||||
}
|
||||
|
@ -611,8 +669,8 @@ AircraftItemPtr LocalAircraftCache::primaryItemFor(AircraftItemPtr item) const
|
|||
if (!item || item->variantOf.isEmpty())
|
||||
return item;
|
||||
|
||||
for (int row=0; row < m_items.size(); ++row) {
|
||||
const AircraftItemPtr p(m_items.at(row));
|
||||
for (int row=0; row < d->m_items.size(); ++row) {
|
||||
const AircraftItemPtr p(d->m_items.at(row));
|
||||
if (p->baseName() == item->variantOf) {
|
||||
return p;
|
||||
}
|
||||
|
@ -625,7 +683,7 @@ AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
|
|||
{
|
||||
int index = findIndexWithUri(aircraftUri);
|
||||
if (index >= 0) {
|
||||
return m_items.at(index);
|
||||
return d->m_items.at(index);
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -633,37 +691,37 @@ AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
|
|||
|
||||
void LocalAircraftCache::abandonCurrentScan()
|
||||
{
|
||||
if (m_scanThread) {
|
||||
if (d->m_scanThread) {
|
||||
// don't fire onScanFinished when the thread ends
|
||||
disconnect(m_scanThread.get(), &AircraftScanThread::finished, this,
|
||||
disconnect(d->m_scanThread.get(), &AircraftScanThread::finished, this,
|
||||
&LocalAircraftCache::onScanFinished);
|
||||
|
||||
m_scanThread->setDone();
|
||||
if (!m_scanThread->wait(2000)) {
|
||||
d->m_scanThread->setDone();
|
||||
if (!d->m_scanThread->wait(2000)) {
|
||||
qWarning() << Q_FUNC_INFO << "scan thread failed to terminate";
|
||||
}
|
||||
m_scanThread.reset();
|
||||
d->m_scanThread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LocalAircraftCache::onScanResults()
|
||||
{
|
||||
if (!m_scanThread) {
|
||||
if (!d->m_scanThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<AircraftItemPtr> newItems = m_scanThread->items();
|
||||
QVector<AircraftItemPtr> newItems = d->m_scanThread->items();
|
||||
if (newItems.isEmpty())
|
||||
return;
|
||||
|
||||
m_items+=newItems;
|
||||
d->m_items+=newItems;
|
||||
emit addedItems(newItems.size());
|
||||
}
|
||||
|
||||
void LocalAircraftCache::onScanFinished()
|
||||
{
|
||||
m_scanThread.reset();
|
||||
d->m_scanThread.reset();
|
||||
emit scanCompleted();
|
||||
}
|
||||
|
||||
|
@ -700,4 +758,46 @@ int LocalAircraftCache::ratingFromProperties(SGPropertyNode* node, int ratingInd
|
|||
return node->getIntValue(names[ratingIndex]);
|
||||
}
|
||||
|
||||
LocalAircraftCache::ParseSetXMLResult
|
||||
LocalAircraftCache::readAircraftProperties(const SGPath &setPath, SGPropertyNode_ptr props)
|
||||
{
|
||||
// it woudld be race-y to touch the reosurce provider while the scan thread is running
|
||||
// and our provider would confuse current-aircraft-dir lookups as well. Since we
|
||||
// can't do thread-specific reosurce providers, we just bail here.
|
||||
if (d->m_scanThread) {
|
||||
return ParseSetXMLResult::Retry;
|
||||
}
|
||||
|
||||
auto rm = simgear::ResourceManager::instance();
|
||||
|
||||
// ensure aircraft relative paths in the -set.xml parsing work
|
||||
|
||||
std::unique_ptr<ScanDirProvider> dp{new ScanDirProvider};
|
||||
rm->addProvider(dp.get());
|
||||
|
||||
// we want to know the aircraft directory the aircraft lives in. This might
|
||||
// be a manually added path (for local aircraft) or the install dir for the
|
||||
// hangar (for packaged aircraft). Becuase -set.xml files are always found at
|
||||
// /some/path/foobarAircraft/<aircraft-name>/some-set.xml, and the path we
|
||||
// we want here is /some/path/foodbarAircraft, we use dirPath twice, and this
|
||||
// works any kind of installed aircraft
|
||||
const SGPath aircraftDirPath = setPath.dirPath().dirPath();
|
||||
dp->setCurrentPath(aircraftDirPath);
|
||||
|
||||
dp->setCurrentAircraftPath(setPath);
|
||||
|
||||
ParseSetXMLResult result = ParseSetXMLResult::Failed;
|
||||
try {
|
||||
readProperties(setPath, props);
|
||||
result = ParseSetXMLResult::Ok;
|
||||
} catch (sg_exception& e) {
|
||||
// malformed include or XML
|
||||
qWarning() << "Problems occurred while parsing" << QString::fromStdString(setPath.utf8Str())
|
||||
<< "\n\t" << QString::fromStdString(e.getFormattedMessage());
|
||||
}
|
||||
|
||||
rm->removeProvider(dp.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
#include "LocalAircraftCache.moc"
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
|
||||
class QDataStream;
|
||||
struct AircraftItem;
|
||||
class AircraftScanThread;
|
||||
class SGPropertyNode;
|
||||
|
||||
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
|
||||
|
@ -114,6 +113,7 @@ public:
|
|||
|
||||
|
||||
void setPaths(QStringList paths);
|
||||
QStringList paths() const;
|
||||
|
||||
void scanDirs();
|
||||
|
||||
|
@ -161,6 +161,20 @@ public:
|
|||
|
||||
// rating order is FDM, Systems, Cockpit, External model
|
||||
static int ratingFromProperties(SGPropertyNode* node, int ratingIndex);
|
||||
|
||||
enum class ParseSetXMLResult {
|
||||
Ok,
|
||||
Failed,
|
||||
Retry ///< aircraft scan in progress, try again later
|
||||
};
|
||||
/**
|
||||
* @brief readAircraftProperties - helper to parse a -set.xml, but with the correct
|
||||
* path setup (root, aircradft dirs, current aircraft dir)
|
||||
* @param path : full path to the -set.xml file
|
||||
* @param props : property node to be populated
|
||||
* @return status indication
|
||||
*/
|
||||
ParseSetXMLResult readAircraftProperties(const SGPath& path, SGPropertyNode_ptr props);
|
||||
signals:
|
||||
|
||||
void scanStarted();
|
||||
|
@ -180,10 +194,8 @@ private:
|
|||
|
||||
void abandonCurrentScan();
|
||||
|
||||
QStringList m_paths;
|
||||
std::unique_ptr<AircraftScanThread> m_scanThread;
|
||||
QVector<AircraftItemPtr> m_items;
|
||||
|
||||
class AircraftCachePrivate;
|
||||
std::unique_ptr<AircraftCachePrivate> d;
|
||||
};
|
||||
|
||||
#endif // LOCALAIRCRAFTCACHE_HXX
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <QDebug>
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlComponent>
|
||||
#include <QTimer>
|
||||
|
||||
#include <simgear/package/Install.hxx>
|
||||
#include <simgear/package/Root.hxx>
|
||||
|
@ -79,16 +80,6 @@ private:
|
|||
QmlAircraftInfo* p;
|
||||
};
|
||||
|
||||
void QmlAircraftInfo::Delegate::finishInstall(InstallRef aInstall, StatusCode aReason)
|
||||
{
|
||||
Q_UNUSED(aReason)
|
||||
if (aInstall->package() == p->packageRef()) {
|
||||
p->_cachedProps.reset();
|
||||
p->checkForStates();
|
||||
p->infoChanged();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
@ -104,22 +95,14 @@ struct StateInfo
|
|||
|
||||
using AircraftStateVec = std::vector<StateInfo>;
|
||||
|
||||
static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
|
||||
static bool readAircraftStates(const SGPropertyNode_ptr root, AircraftStateVec& result)
|
||||
{
|
||||
SGPropertyNode_ptr root(new SGPropertyNode);
|
||||
try {
|
||||
readProperties(setXMLPath, root);
|
||||
} catch (sg_exception&) {
|
||||
// malformed include or XML, just bail
|
||||
return {};
|
||||
}
|
||||
|
||||
result.clear();
|
||||
if (!root->getNode("sim/state")) {
|
||||
return {};
|
||||
return false; // no states
|
||||
}
|
||||
|
||||
auto nodes = root->getNode("sim")->getChildren("state");
|
||||
AircraftStateVec result;
|
||||
result.reserve(nodes.size());
|
||||
for (auto cn : nodes) {
|
||||
string_list stateNames;
|
||||
|
@ -138,7 +121,7 @@ static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
|
|||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
|
||||
QString humanNameFromStateTag(const std::string& tag)
|
||||
|
@ -169,15 +152,27 @@ class StatesModel : public QAbstractListModel
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StatesModel()
|
||||
StatesModel(QObject* pr) : QAbstractListModel(pr)
|
||||
{
|
||||
}
|
||||
|
||||
StatesModel(const AircraftStateVec& states) :
|
||||
_data(states)
|
||||
void clear()
|
||||
{
|
||||
beginResetModel();
|
||||
_data.clear();
|
||||
_explicitAutoState = false;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void initWithStates(const AircraftStateVec& states)
|
||||
{
|
||||
beginResetModel();
|
||||
_data = states;
|
||||
_explicitAutoState = false;
|
||||
|
||||
// we use an empty model for aircraft with no states defined
|
||||
if (states.empty()) {
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -195,6 +190,8 @@ public:
|
|||
} else {
|
||||
_data.insert(_data.begin(), {{"auto"}, {}, tr("Select state based on startup position.")});
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
Q_INVOKABLE int indexForTag(QString s) const
|
||||
|
@ -300,6 +297,22 @@ private:
|
|||
bool _explicitAutoState = false;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void QmlAircraftInfo::Delegate::finishInstall(InstallRef aInstall, StatusCode aReason)
|
||||
{
|
||||
Q_UNUSED(aReason)
|
||||
if (aInstall->package() == p->packageRef()) {
|
||||
p->_cachedProps.reset();
|
||||
if (p->_statesModel) {
|
||||
p->_statesModel->clear();
|
||||
}
|
||||
p->_statesChecked = false;
|
||||
p->infoChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
QmlAircraftInfo::QmlAircraftInfo(QObject *parent)
|
||||
|
@ -592,53 +605,49 @@ AircraftItemPtr QmlAircraftInfo::resolveItem() const
|
|||
return _item;
|
||||
}
|
||||
|
||||
void QmlAircraftInfo::checkForStates()
|
||||
void QmlAircraftInfo::validateStates() const
|
||||
{
|
||||
QString path = pathOnDisk();
|
||||
if (path.isEmpty()) {
|
||||
_statesModel.reset(new StatesModel);
|
||||
emit infoChanged();
|
||||
return;
|
||||
// after calling this, StatesModel must exist, but can be empty
|
||||
if (!_statesModel) {
|
||||
_statesModel = new StatesModel(const_cast<QmlAircraftInfo*>(this));
|
||||
}
|
||||
|
||||
const auto sgp = SGPath::fromUtf8(path.toUtf8().toStdString());
|
||||
if (!sgp.exists()) {
|
||||
_statesModel.reset(new StatesModel);
|
||||
emit infoChanged();
|
||||
if (_statesChecked)
|
||||
return;
|
||||
}
|
||||
|
||||
auto states = readAircraftStates(sgp);
|
||||
if (states.empty()) {
|
||||
_statesModel.reset(new StatesModel);
|
||||
emit infoChanged();
|
||||
validateLocalProps();
|
||||
if (!_cachedProps)
|
||||
return;
|
||||
}
|
||||
|
||||
_statesModel.reset(new StatesModel(states));
|
||||
emit infoChanged();
|
||||
AircraftStateVec statesData;
|
||||
readAircraftStates(_cachedProps, statesData);
|
||||
_statesModel->initWithStates(statesData);
|
||||
_statesChecked = true;
|
||||
}
|
||||
|
||||
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;
|
||||
// clear any previous states if we're reusing this object
|
||||
if (_statesModel)
|
||||
_statesModel->clear();
|
||||
|
||||
path = install->path();
|
||||
}
|
||||
_statesChecked = false;
|
||||
|
||||
SGPath path = SGPath::fromUtf8(pathOnDisk().toStdString());
|
||||
if (!path.exists())
|
||||
return;
|
||||
_cachedProps = new SGPropertyNode;
|
||||
try {
|
||||
readProperties(path, _cachedProps);
|
||||
} catch (sg_exception&) {
|
||||
auto r = LocalAircraftCache::instance()->readAircraftProperties(path, _cachedProps);
|
||||
if (r == LocalAircraftCache::ParseSetXMLResult::Retry) {
|
||||
_cachedProps.reset();
|
||||
|
||||
// give the AircraftScsn threa d abit more time
|
||||
QTimer::singleShot(2000, this, &QmlAircraftInfo::retryValidateLocalProps);
|
||||
} else if (r == LocalAircraftCache::ParseSetXMLResult::Ok) {
|
||||
// we're good
|
||||
} else {
|
||||
// failure
|
||||
_cachedProps.reset();
|
||||
}
|
||||
}
|
||||
|
@ -651,8 +660,10 @@ void QmlAircraftInfo::setUri(QUrl u)
|
|||
|
||||
_item.clear();
|
||||
_package.clear();
|
||||
_statesModel.reset(new StatesModel);
|
||||
_cachedProps.clear();
|
||||
_statesChecked = false;
|
||||
if (_statesModel)
|
||||
_statesModel->clear();
|
||||
|
||||
if (u.isLocalFile()) {
|
||||
_item = LocalAircraftCache::instance()->findItemWithUri(u);
|
||||
|
@ -679,8 +690,6 @@ void QmlAircraftInfo::setUri(QUrl u)
|
|||
}
|
||||
}
|
||||
|
||||
checkForStates();
|
||||
|
||||
emit uriChanged();
|
||||
emit infoChanged();
|
||||
emit downloadChanged();
|
||||
|
@ -702,8 +711,9 @@ void QmlAircraftInfo::setVariant(quint32 variant)
|
|||
|
||||
_variant = variant;
|
||||
_cachedProps.clear();
|
||||
|
||||
checkForStates();
|
||||
_statesChecked = false;
|
||||
if (_statesModel)
|
||||
_statesModel->clear();
|
||||
|
||||
emit infoChanged();
|
||||
emit variantChanged(_variant);
|
||||
|
@ -722,6 +732,12 @@ void QmlAircraftInfo::onFavouriteChanged(QUrl u)
|
|||
emit favouriteChanged();
|
||||
}
|
||||
|
||||
void QmlAircraftInfo::retryValidateLocalProps()
|
||||
{
|
||||
qInfo() << Q_FUNC_INFO << "Retrying validation of local props for:" << uri();
|
||||
validateLocalProps();
|
||||
}
|
||||
|
||||
QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p)
|
||||
{
|
||||
if (p->hasTag("needs-maintenance")) {
|
||||
|
@ -806,22 +822,26 @@ bool QmlAircraftInfo::isPackaged() const
|
|||
|
||||
bool QmlAircraftInfo::hasStates() const
|
||||
{
|
||||
validateStates();
|
||||
return !_statesModel->isEmpty();
|
||||
}
|
||||
|
||||
bool QmlAircraftInfo::hasState(QString name) const
|
||||
{
|
||||
validateStates();
|
||||
return _statesModel->hasState(name);
|
||||
}
|
||||
|
||||
bool QmlAircraftInfo::haveExplicitAutoState() const
|
||||
{
|
||||
validateStates();
|
||||
return _statesModel->hasExplicitAuto();
|
||||
}
|
||||
|
||||
StatesModel *QmlAircraftInfo::statesModel()
|
||||
{
|
||||
return _statesModel.data();
|
||||
validateStates();
|
||||
return _statesModel;
|
||||
}
|
||||
|
||||
QuantityValue QmlAircraftInfo::cruiseSpeed() const
|
||||
|
@ -886,11 +906,13 @@ QString QmlAircraftInfo::icaoType() const
|
|||
|
||||
bool QmlAircraftInfo::isSpeedBelowLimits(QuantityValue speed) const
|
||||
{
|
||||
Q_UNUSED(speed)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QmlAircraftInfo::isAltitudeBelowLimits(QuantityValue speed) const
|
||||
{
|
||||
Q_UNUSED(speed)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ class QmlAircraftInfo : public QObject
|
|||
Q_PROPERTY(QString icaoType READ icaoType NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(bool hasStates READ hasStates NOTIFY infoChanged)
|
||||
Q_PROPERTY(StatesModel* statesModel READ statesModel NOTIFY infoChanged)
|
||||
Q_PROPERTY(StatesModel* statesModel READ statesModel CONSTANT)
|
||||
|
||||
Q_PROPERTY(bool favourite READ favourite WRITE setFavourite NOTIFY favouriteChanged)
|
||||
public:
|
||||
|
@ -147,10 +147,12 @@ public slots:
|
|||
|
||||
private slots:
|
||||
void onFavouriteChanged(QUrl u);
|
||||
void retryValidateLocalProps();
|
||||
|
||||
private:
|
||||
AircraftItemPtr resolveItem() const;
|
||||
void checkForStates();
|
||||
|
||||
void validateStates() const;
|
||||
void validateLocalProps() const;
|
||||
|
||||
class Delegate;
|
||||
|
@ -160,7 +162,11 @@ private:
|
|||
AircraftItemPtr _item;
|
||||
quint32 _variant = 0;
|
||||
quint64 _downloadBytes = 0;
|
||||
QScopedPointer<StatesModel> _statesModel;
|
||||
|
||||
/// either null or owned by us
|
||||
mutable StatesModel* _statesModel = nullptr;
|
||||
|
||||
mutable bool _statesChecked = false;
|
||||
|
||||
/// if the aircraft is locally installed, this is the cached
|
||||
/// parsed contents of the -set.xml.
|
||||
|
|
Loading…
Add table
Reference in a new issue