Seperate aircraft cache from the model.
Allows exposing aircraft data to QML (via a helper object) outside the context of the model.
This commit is contained in:
parent
78950fea57
commit
041b9527d3
12 changed files with 1111 additions and 553 deletions
51
src/GUI/AircraftDetailsView.qml
Normal file
51
src/GUI/AircraftDetailsView.qml
Normal file
|
@ -0,0 +1,51 @@
|
|||
import QtQuick 2.0
|
||||
import FGLauncher 1.0
|
||||
|
||||
Item {
|
||||
|
||||
property alias aurcradftURI: aircraft.uri
|
||||
|
||||
|
||||
AircraftInfo
|
||||
{
|
||||
id: aircraft
|
||||
}
|
||||
|
||||
Column {
|
||||
Text {
|
||||
id: aircraftName
|
||||
}
|
||||
|
||||
Image {
|
||||
id: preview
|
||||
|
||||
|
||||
// selector overlay
|
||||
|
||||
// left / right arrows
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: previewCycleTimer
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
id: aircraftDescription
|
||||
}
|
||||
|
||||
Text {
|
||||
id: aircraftAuthors
|
||||
}
|
||||
|
||||
// info button
|
||||
|
||||
// version warning!
|
||||
|
||||
// ratings box
|
||||
|
||||
// package size
|
||||
|
||||
// install / download / update button
|
||||
} // main layout column
|
||||
}
|
|
@ -20,11 +20,6 @@
|
|||
|
||||
#include "AircraftModel.hxx"
|
||||
|
||||
#include <QDir>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QDataStream>
|
||||
#include <QSettings>
|
||||
#include <QDebug>
|
||||
#include <QSharedPointer>
|
||||
|
@ -41,317 +36,13 @@
|
|||
#include <Main/globals.hxx>
|
||||
#include <Include/version.h>
|
||||
|
||||
#include "QmlAircraftInfo.hxx"
|
||||
|
||||
const int STANDARD_THUMBNAIL_HEIGHT = 128;
|
||||
const int STANDARD_THUMBNAIL_WIDTH = 172;
|
||||
static quint32 CACHE_VERSION = 8;
|
||||
|
||||
using namespace simgear::pkg;
|
||||
|
||||
AircraftItem::AircraftItem()
|
||||
{
|
||||
}
|
||||
|
||||
AircraftItem::AircraftItem(QDir dir, QString filePath)
|
||||
{
|
||||
SGPropertyNode root;
|
||||
readProperties(filePath.toStdString(), &root);
|
||||
|
||||
if (!root.hasChild("sim")) {
|
||||
throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr sim = root.getNode("sim");
|
||||
|
||||
path = filePath;
|
||||
pathModTime = QFileInfo(path).lastModified();
|
||||
if (sim->getBoolValue("exclude-from-gui", false)) {
|
||||
excluded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
description = sim->getStringValue("description");
|
||||
authors = sim->getStringValue("author");
|
||||
|
||||
if (sim->hasChild("rating")) {
|
||||
SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
|
||||
ratings[0] = ratingsNode->getIntValue("FDM");
|
||||
ratings[1] = ratingsNode->getIntValue("systems");
|
||||
ratings[2] = ratingsNode->getIntValue("cockpit");
|
||||
ratings[3] = ratingsNode->getIntValue("model");
|
||||
|
||||
}
|
||||
|
||||
if (sim->hasChild("long-description")) {
|
||||
// clean up any XML whitspace in the text.
|
||||
longDescription = QString(sim->getStringValue("long-description")).simplified();
|
||||
}
|
||||
|
||||
if (sim->hasChild("variant-of")) {
|
||||
variantOf = sim->getStringValue("variant-of");
|
||||
} else {
|
||||
isPrimary = true;
|
||||
}
|
||||
|
||||
if (sim->hasChild("primary-set")) {
|
||||
isPrimary = sim->getBoolValue("primary-set");
|
||||
}
|
||||
|
||||
if (sim->hasChild("tags")) {
|
||||
SGPropertyNode_ptr tagsNode = sim->getChild("tags");
|
||||
int nChildren = tagsNode->nChildren();
|
||||
for (int i = 0; i < nChildren; i++) {
|
||||
const SGPropertyNode* c = tagsNode->getChild(i);
|
||||
if (strcmp(c->getName(), "tag") == 0) {
|
||||
const char* tagName = c->getStringValue();
|
||||
usesHeliports |= (strcmp(tagName, "helicopter") == 0);
|
||||
// could also consider vtol tag?
|
||||
usesSeaports |= (strcmp(tagName, "seaplane") == 0);
|
||||
usesSeaports |= (strcmp(tagName, "floats") == 0);
|
||||
|
||||
needsMaintenance |= (strcmp(tagName, "needs-maintenance") == 0);
|
||||
}
|
||||
} // of tags iteration
|
||||
} // of set-xml has tags
|
||||
|
||||
if (sim->hasChild("previews")) {
|
||||
SGPropertyNode_ptr previewsNode = sim->getChild("previews");
|
||||
for (auto previewNode : previewsNode->getChildren("preview")) {
|
||||
// add file path as url
|
||||
QString pathInXml = QString::fromStdString(previewNode->getStringValue("path"));
|
||||
QString previewPath = dir.absoluteFilePath(pathInXml);
|
||||
previews.append(QUrl::fromLocalFile(previewPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (sim->hasChild("thumbnail")) {
|
||||
thumbnailPath = sim->getStringValue("thumbnail");
|
||||
} else {
|
||||
thumbnailPath = "thumbnail.jpg";
|
||||
}
|
||||
|
||||
if (sim->hasChild("minimum-fg-version")) {
|
||||
minFGVersion = sim->getStringValue("minimum-fg-version");
|
||||
}
|
||||
}
|
||||
|
||||
QString AircraftItem::baseName() const
|
||||
{
|
||||
QString fn = QFileInfo(path).fileName();
|
||||
fn.truncate(fn.count() - 8);
|
||||
return fn;
|
||||
}
|
||||
|
||||
void AircraftItem::fromDataStream(QDataStream& ds)
|
||||
{
|
||||
ds >> path >> pathModTime >> excluded;
|
||||
if (excluded) {
|
||||
return;
|
||||
}
|
||||
|
||||
ds >> description >> longDescription >> authors >> variantOf >> isPrimary;
|
||||
for (int i=0; i<4; ++i) ds >> ratings[i];
|
||||
ds >> previews;
|
||||
ds >> thumbnailPath;
|
||||
ds >> minFGVersion;
|
||||
ds >> needsMaintenance >> usesHeliports >> usesSeaports;
|
||||
}
|
||||
|
||||
void AircraftItem::toDataStream(QDataStream& ds) const
|
||||
{
|
||||
ds << path << pathModTime << excluded;
|
||||
if (excluded) {
|
||||
return;
|
||||
}
|
||||
|
||||
ds << description << longDescription << authors << variantOf << isPrimary;
|
||||
for (int i=0; i<4; ++i) ds << ratings[i];
|
||||
ds << previews;
|
||||
ds << thumbnailPath;
|
||||
ds << minFGVersion;
|
||||
ds << needsMaintenance << usesHeliports << usesSeaports;
|
||||
}
|
||||
|
||||
QPixmap AircraftItem::thumbnail(bool loadIfRequired) const
|
||||
{
|
||||
if (m_thumbnail.isNull() && loadIfRequired) {
|
||||
QFileInfo info(path);
|
||||
QDir dir = info.dir();
|
||||
if (dir.exists(thumbnailPath)) {
|
||||
m_thumbnail.load(dir.filePath(thumbnailPath));
|
||||
// resize to the standard size
|
||||
if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) {
|
||||
m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT, Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_thumbnail;
|
||||
}
|
||||
|
||||
class AircraftScanThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AircraftScanThread(QStringList dirsToScan) :
|
||||
m_dirs(dirsToScan),
|
||||
m_done(false)
|
||||
{
|
||||
}
|
||||
|
||||
~AircraftScanThread()
|
||||
{
|
||||
}
|
||||
|
||||
/** thread-safe access to items already scanned */
|
||||
QVector<AircraftItemPtr> items()
|
||||
{
|
||||
QVector<AircraftItemPtr> result;
|
||||
QMutexLocker g(&m_lock);
|
||||
result.swap(m_items);
|
||||
g.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void setDone()
|
||||
{
|
||||
m_done = true;
|
||||
}
|
||||
Q_SIGNALS:
|
||||
void addedItems();
|
||||
|
||||
protected:
|
||||
virtual void run()
|
||||
{
|
||||
readCache();
|
||||
|
||||
Q_FOREACH(QString d, m_dirs) {
|
||||
scanAircraftDir(QDir(d));
|
||||
if (m_done) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
writeCache();
|
||||
}
|
||||
|
||||
private:
|
||||
void readCache()
|
||||
{
|
||||
QSettings settings;
|
||||
QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
|
||||
if (!cacheData.isEmpty()) {
|
||||
QDataStream ds(cacheData);
|
||||
quint32 count, cacheVersion;
|
||||
ds >> cacheVersion >> count;
|
||||
|
||||
if (cacheVersion != CACHE_VERSION) {
|
||||
return; // mis-matched cache, version, drop
|
||||
}
|
||||
|
||||
for (quint32 i=0; i<count; ++i) {
|
||||
AircraftItemPtr item(new AircraftItem);
|
||||
item->fromDataStream(ds);
|
||||
|
||||
QFileInfo finfo(item->path);
|
||||
if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
|
||||
// corresponding -set.xml file still exists and is
|
||||
// unmodified
|
||||
m_cachedItems[item->path] = item;
|
||||
}
|
||||
} // of cached item iteration
|
||||
}
|
||||
}
|
||||
|
||||
void writeCache()
|
||||
{
|
||||
QSettings settings;
|
||||
QByteArray cacheData;
|
||||
{
|
||||
QDataStream ds(&cacheData, QIODevice::WriteOnly);
|
||||
quint32 count = m_nextCache.count();
|
||||
ds << CACHE_VERSION << count;
|
||||
|
||||
Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) {
|
||||
item->toDataStream(ds);
|
||||
}
|
||||
}
|
||||
|
||||
settings.setValue("aircraft-cache", cacheData);
|
||||
}
|
||||
|
||||
void scanAircraftDir(QDir path)
|
||||
{
|
||||
QTime t;
|
||||
t.start();
|
||||
|
||||
QStringList filters;
|
||||
filters << "*-set.xml";
|
||||
Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||
QDir childDir(child.absoluteFilePath());
|
||||
QMap<QString, AircraftItemPtr> baseAircraft;
|
||||
QList<AircraftItemPtr> variants;
|
||||
|
||||
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
|
||||
try {
|
||||
QString absolutePath = xmlChild.absoluteFilePath();
|
||||
AircraftItemPtr item;
|
||||
|
||||
if (m_cachedItems.contains(absolutePath)) {
|
||||
item = m_cachedItems.value(absolutePath);
|
||||
} else {
|
||||
item = AircraftItemPtr(new AircraftItem(childDir, absolutePath));
|
||||
}
|
||||
|
||||
m_nextCache[absolutePath] = item;
|
||||
|
||||
if (item->excluded) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->isPrimary) {
|
||||
baseAircraft.insert(item->baseName(), item);
|
||||
} else {
|
||||
variants.append(item);
|
||||
}
|
||||
} catch (sg_exception& e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_done) {
|
||||
return;
|
||||
}
|
||||
} // of set.xml iteration
|
||||
|
||||
// bind variants to their principals
|
||||
Q_FOREACH(AircraftItemPtr item, variants) {
|
||||
if (!baseAircraft.contains(item->variantOf)) {
|
||||
qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
|
||||
continue;
|
||||
}
|
||||
|
||||
baseAircraft.value(item->variantOf)->variants.append(item);
|
||||
}
|
||||
|
||||
// lock mutex while we modify the items array
|
||||
{
|
||||
QMutexLocker g(&m_lock);
|
||||
m_items+=(baseAircraft.values().toVector());
|
||||
}
|
||||
|
||||
emit addedItems();
|
||||
} // of subdir iteration
|
||||
}
|
||||
|
||||
QMutex m_lock;
|
||||
QStringList m_dirs;
|
||||
QVector<AircraftItemPtr> m_items;
|
||||
|
||||
QMap<QString, AircraftItemPtr > m_cachedItems;
|
||||
QMap<QString, AircraftItemPtr > m_nextCache;
|
||||
|
||||
bool m_done;
|
||||
};
|
||||
|
||||
class PackageDelegate : public simgear::pkg::Delegate
|
||||
{
|
||||
public:
|
||||
|
@ -473,7 +164,7 @@ private:
|
|||
}
|
||||
|
||||
int offset = std::distance(m_model->m_packages.begin(), it);
|
||||
return m_model->index(offset + m_model->m_items.size());
|
||||
return m_model->index(offset + m_model->m_cachedLocalAircraftCount);
|
||||
}
|
||||
|
||||
AircraftItemModel* m_model;
|
||||
|
@ -482,11 +173,15 @@ private:
|
|||
AircraftItemModel::AircraftItemModel(QObject* pr) :
|
||||
QAbstractListModel(pr)
|
||||
{
|
||||
auto cache = LocalAircraftCache::instance();
|
||||
connect(cache, &LocalAircraftCache::scanStarted,
|
||||
this, &AircraftItemModel::onScanStarted);
|
||||
connect(cache, &LocalAircraftCache::addedItems,
|
||||
this, &AircraftItemModel::onScanAddedItems);
|
||||
}
|
||||
|
||||
AircraftItemModel::~AircraftItemModel()
|
||||
{
|
||||
abandonCurrentScan();
|
||||
delete m_delegate;
|
||||
}
|
||||
|
||||
|
@ -506,56 +201,22 @@ void AircraftItemModel::setPackageRoot(const simgear::pkg::RootRef& root)
|
|||
}
|
||||
}
|
||||
|
||||
void AircraftItemModel::setPaths(QStringList paths)
|
||||
void AircraftItemModel::onScanStarted()
|
||||
{
|
||||
m_paths = paths;
|
||||
}
|
||||
|
||||
void AircraftItemModel::scanDirs()
|
||||
{
|
||||
abandonCurrentScan();
|
||||
|
||||
const int numToRemove = m_items.size();
|
||||
if (numToRemove > 0) {
|
||||
const int numToRemove = m_cachedLocalAircraftCount;
|
||||
if (numToRemove > 0) {
|
||||
int lastRow = numToRemove - 1;
|
||||
beginRemoveRows(QModelIndex(), 0, lastRow);
|
||||
m_items.remove(0, numToRemove);
|
||||
m_delegateStates.remove(0, numToRemove);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
QStringList dirs = m_paths;
|
||||
|
||||
Q_FOREACH(SGPath ap, globals->get_aircraft_paths()) {
|
||||
dirs << QString::fromStdString(ap.utf8Str());
|
||||
}
|
||||
|
||||
SGPath rootAircraft(globals->get_fg_root());
|
||||
rootAircraft.append("Aircraft");
|
||||
dirs << QString::fromStdString(rootAircraft.utf8Str());
|
||||
|
||||
m_scanThread = new AircraftScanThread(dirs);
|
||||
connect(m_scanThread, &AircraftScanThread::finished, this,
|
||||
&AircraftItemModel::onScanFinished);
|
||||
connect(m_scanThread, &AircraftScanThread::addedItems,
|
||||
this, &AircraftItemModel::onScanResults);
|
||||
m_scanThread->start();
|
||||
}
|
||||
|
||||
void AircraftItemModel::abandonCurrentScan()
|
||||
{
|
||||
if (m_scanThread) {
|
||||
m_scanThread->setDone();
|
||||
m_scanThread->wait(1000);
|
||||
delete m_scanThread;
|
||||
m_scanThread = NULL;
|
||||
m_cachedLocalAircraftCount = 0;
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void AircraftItemModel::refreshPackages()
|
||||
{
|
||||
simgear::pkg::PackageList newPkgs = m_packageRoot->allPackages();
|
||||
const int firstRow = m_items.size();
|
||||
const int firstRow = m_cachedLocalAircraftCount;
|
||||
const int newSize = newPkgs.size();
|
||||
const int newTotalSize = firstRow + newSize;
|
||||
|
||||
|
@ -589,7 +250,7 @@ void AircraftItemModel::refreshPackages()
|
|||
|
||||
int AircraftItemModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return m_items.size() + m_packages.size();
|
||||
return m_cachedLocalAircraftCount + m_packages.size();
|
||||
}
|
||||
|
||||
QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
|
||||
|
@ -599,8 +260,8 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
|
|||
return m_delegateStates.at(row).variant;
|
||||
}
|
||||
|
||||
if (row >= m_items.size()) {
|
||||
quint32 packageIndex = row - m_items.size();
|
||||
if (row >= m_cachedLocalAircraftCount) {
|
||||
quint32 packageIndex = row - m_cachedLocalAircraftCount;
|
||||
const PackageRef& pkg(m_packages[packageIndex]);
|
||||
InstallRef ex = pkg->existingInstall();
|
||||
|
||||
|
@ -614,7 +275,7 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
|
|||
|
||||
return dataFromPackage(pkg, m_delegateStates.at(row), role);
|
||||
} else {
|
||||
const AircraftItemPtr item(m_items.at(row));
|
||||
const AircraftItemPtr item(LocalAircraftCache::instance()->itemAt(row));
|
||||
return dataFromItem(item, m_delegateStates.at(row), role);
|
||||
}
|
||||
}
|
||||
|
@ -694,7 +355,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta
|
|||
{
|
||||
return 0;
|
||||
} else if (role == AircraftStatusRole) {
|
||||
return itemAircraftStatus(item, state);
|
||||
return item->status(0 /* variant is always 0 */);
|
||||
} else if (role == AircraftMinVersionRole) {
|
||||
return item->minFGVersion;
|
||||
}
|
||||
|
@ -784,7 +445,7 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const Delega
|
|||
}
|
||||
return ratings->getChild(ratingIndex)->getIntValue();
|
||||
} else if (role == AircraftStatusRole) {
|
||||
return packageAircraftStatus(item, state);
|
||||
return QmlAircraftInfo::packageAircraftStatus(item);
|
||||
} else if (role == AircraftMinVersionRole) {
|
||||
const std::string v = item->properties()->getStringValue("minimum-fg-version");
|
||||
if (!v.empty()) {
|
||||
|
@ -860,37 +521,6 @@ QVariant AircraftItemModel::packagePreviews(PackageRef p, const DelegateState& d
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
QVariant AircraftItemModel::itemAircraftStatus(AircraftItemPtr item, const DelegateState& ds) const
|
||||
{
|
||||
if (item->needsMaintenance) {
|
||||
return AircraftUnmaintained;
|
||||
}
|
||||
|
||||
if (item->minFGVersion.isEmpty()) {
|
||||
return AircraftOk;
|
||||
}
|
||||
|
||||
const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION,
|
||||
item->minFGVersion.toStdString(), 2);
|
||||
return (c < 0) ? AircraftNeedsNewerSimulator : AircraftOk;
|
||||
}
|
||||
|
||||
QVariant AircraftItemModel::packageAircraftStatus(PackageRef p, const DelegateState& ds) const
|
||||
{
|
||||
if (p->hasTag("needs-maintenance")) {
|
||||
return AircraftUnmaintained;
|
||||
}
|
||||
|
||||
if (!p->properties()->hasChild("minimum-fg-version")) {
|
||||
return 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) ? AircraftNeedsNewerSimulator : AircraftOk;
|
||||
}
|
||||
|
||||
bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
int row = index.row();
|
||||
|
@ -946,23 +576,13 @@ QModelIndex AircraftItemModel::indexOfAircraftURI(QUrl uri) const
|
|||
}
|
||||
|
||||
if (uri.isLocalFile()) {
|
||||
QString path = uri.toLocalFile();
|
||||
for (int row=0; row <m_items.size(); ++row) {
|
||||
const AircraftItemPtr item(m_items.at(row));
|
||||
if (item->path == path) {
|
||||
return index(row);
|
||||
}
|
||||
|
||||
// check variants too
|
||||
for (int vr=0; vr < item->variants.size(); ++vr) {
|
||||
if (item->variants.at(vr)->path == path) {
|
||||
return index(row);
|
||||
}
|
||||
}
|
||||
int row = LocalAircraftCache::instance()->findIndexWithUri(uri);
|
||||
if (row >= 0) {
|
||||
return index(row);
|
||||
}
|
||||
} else if (uri.scheme() == "package") {
|
||||
QString ident = uri.path();
|
||||
int rowOffset = m_items.size();
|
||||
int rowOffset = m_cachedLocalAircraftCount;
|
||||
|
||||
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
|
||||
if (pkg) {
|
||||
|
@ -991,27 +611,25 @@ void AircraftItemModel::selectVariantForAircraftURI(QUrl uri)
|
|||
QModelIndex modelIndex;
|
||||
|
||||
if (uri.isLocalFile()) {
|
||||
QString path = uri.toLocalFile();
|
||||
for (int row=0; row <m_items.size(); ++row) {
|
||||
const AircraftItemPtr item(m_items.at(row));
|
||||
if (item->path == path) {
|
||||
modelIndex = index(row);
|
||||
variantIndex = 0;
|
||||
break;
|
||||
}
|
||||
int row = LocalAircraftCache::instance()->findIndexWithUri(uri);
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check variants too
|
||||
for (int vr=0; vr < item->variants.size(); ++vr) {
|
||||
if (item->variants.at(vr)->path == path) {
|
||||
modelIndex = index(row);
|
||||
variantIndex = vr + 1;
|
||||
break;
|
||||
}
|
||||
modelIndex = index(row);
|
||||
// now check if we are actually selecting a variant
|
||||
const AircraftItemPtr item = LocalAircraftCache::instance()->itemAt(row);
|
||||
|
||||
const QString path = uri.toLocalFile();
|
||||
for (int vr=0; vr < item->variants.size(); ++vr) {
|
||||
if (item->variants.at(vr)->path == path) {
|
||||
variantIndex = vr + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (uri.scheme() == "package") {
|
||||
QString ident = uri.path();
|
||||
int rowOffset = m_items.size();
|
||||
int rowOffset = m_cachedLocalAircraftCount;
|
||||
|
||||
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
|
||||
if (pkg) {
|
||||
|
@ -1036,18 +654,16 @@ void AircraftItemModel::selectVariantForAircraftURI(QUrl uri)
|
|||
QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
|
||||
{
|
||||
if (uri.isLocalFile()) {
|
||||
QString path = uri.toLocalFile();
|
||||
for (int row=0; row <m_items.size(); ++row) {
|
||||
const AircraftItemPtr item(m_items.at(row));
|
||||
if (item->path == path) {
|
||||
return item->description;
|
||||
}
|
||||
AircraftItemPtr item = LocalAircraftCache::instance()->findItemWithUri(uri);
|
||||
const QString path = uri.toLocalFile();
|
||||
if (item->path == path) {
|
||||
return item->description;
|
||||
}
|
||||
|
||||
// check variants too
|
||||
for (int vr=0; vr < item->variants.size(); ++vr) {
|
||||
if (item->variants.at(vr)->path == path) {
|
||||
return item->description;
|
||||
}
|
||||
// check variants too
|
||||
for (int vr=0; vr < item->variants.size(); ++vr) {
|
||||
if (item->variants.at(vr)->path == path) {
|
||||
return item->description;
|
||||
}
|
||||
}
|
||||
} else if (uri.scheme() == "package") {
|
||||
|
@ -1065,32 +681,25 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
|
|||
return QString();
|
||||
}
|
||||
|
||||
void AircraftItemModel::onScanResults()
|
||||
void AircraftItemModel::onScanAddedItems(int count)
|
||||
{
|
||||
QVector<AircraftItemPtr> newItems = m_scanThread->items();
|
||||
QVector<AircraftItemPtr> newItems = LocalAircraftCache::instance()->newestItems(count);
|
||||
if (newItems.isEmpty())
|
||||
return;
|
||||
|
||||
int firstRow = m_items.count();
|
||||
int lastRow = firstRow + newItems.count() - 1;
|
||||
int firstRow = m_cachedLocalAircraftCount;
|
||||
int lastRow = firstRow + count - 1;
|
||||
beginInsertRows(QModelIndex(), firstRow, lastRow);
|
||||
m_items+=newItems;
|
||||
m_cachedLocalAircraftCount += count;
|
||||
|
||||
// default variants in all cases
|
||||
for (int i=0; i< newItems.count(); ++i) {
|
||||
m_delegateStates.insert(firstRow + i, DelegateState());
|
||||
for (int i=0; i< count; ++i) {
|
||||
m_delegateStates.insert(firstRow + i, {});
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void AircraftItemModel::onScanFinished()
|
||||
{
|
||||
delete m_scanThread;
|
||||
m_scanThread = NULL;
|
||||
emit scanCompleted();
|
||||
}
|
||||
|
||||
void AircraftItemModel::installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason)
|
||||
{
|
||||
QString msg;
|
||||
|
@ -1120,11 +729,11 @@ void AircraftItemModel::installSucceeded(QModelIndex index)
|
|||
|
||||
bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
|
||||
{
|
||||
if (index.row() < m_items.size()) {
|
||||
if (index.row() < m_cachedLocalAircraftCount) {
|
||||
return true; // local file, always runnable
|
||||
}
|
||||
|
||||
quint32 packageIndex = index.row() - m_items.size();
|
||||
quint32 packageIndex = index.row() - m_cachedLocalAircraftCount;
|
||||
const PackageRef& pkg(m_packages[packageIndex]);
|
||||
InstallRef ex = pkg->existingInstall();
|
||||
if (!ex.valid()) {
|
||||
|
@ -1134,29 +743,6 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
|
|||
return !ex->isDownloading();
|
||||
}
|
||||
|
||||
bool AircraftItemModel::isCandidateAircraftPath(QString path)
|
||||
{
|
||||
QStringList filters;
|
||||
filters << "*-set.xml";
|
||||
int dirCount = 0,
|
||||
setXmlCount = 0;
|
||||
|
||||
QDir d(path);
|
||||
Q_FOREACH(QFileInfo child, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||
QDir childDir(child.absoluteFilePath());
|
||||
++dirCount;
|
||||
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
|
||||
++setXmlCount;
|
||||
}
|
||||
|
||||
if ((setXmlCount > 0) || (dirCount > 10)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (setXmlCount > 0);
|
||||
}
|
||||
|
||||
int AircraftItemModel::aircraftNeedingUpdated() const
|
||||
{
|
||||
return m_cachedUpdateCount;
|
||||
|
@ -1176,4 +762,3 @@ void AircraftItemModel::setShowUpdateAll(bool showUpdateAll)
|
|||
emit aircraftNeedingUpdatedChanged();
|
||||
}
|
||||
|
||||
#include "AircraftModel.moc"
|
||||
|
|
|
@ -22,13 +22,13 @@
|
|||
#define FG_GUI_AIRCRAFT_MODEL
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QPixmap>
|
||||
#include <QStringList>
|
||||
#include <QSharedPointer>
|
||||
#include <QUrl>
|
||||
|
||||
#include "LocalAircraftCache.hxx"
|
||||
|
||||
#include <simgear/package/Delegate.hxx>
|
||||
#include <simgear/package/Root.hxx>
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
|
@ -62,49 +62,10 @@ const int AircraftHasPreviewsRole = Qt::UserRole + 24;
|
|||
const int AircraftRatingRole = Qt::UserRole + 100;
|
||||
const int AircraftVariantDescriptionRole = Qt::UserRole + 200;
|
||||
|
||||
class AircraftScanThread;
|
||||
class QDataStream;
|
||||
class PackageDelegate;
|
||||
struct AircraftItem;
|
||||
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
|
||||
|
||||
Q_DECLARE_METATYPE(simgear::pkg::PackageRef)
|
||||
|
||||
struct AircraftItem
|
||||
{
|
||||
AircraftItem();
|
||||
|
||||
AircraftItem(QDir dir, QString filePath);
|
||||
|
||||
// the file-name without -set.xml suffix
|
||||
QString baseName() const;
|
||||
|
||||
void fromDataStream(QDataStream& ds);
|
||||
|
||||
void toDataStream(QDataStream& ds) const;
|
||||
|
||||
QPixmap thumbnail(bool loadIfRequired = true) const;
|
||||
|
||||
bool excluded = false;
|
||||
QString path;
|
||||
QString description;
|
||||
QString longDescription;
|
||||
QString authors;
|
||||
int ratings[4] = {0, 0, 0, 0};
|
||||
QString variantOf;
|
||||
QDateTime pathModTime;
|
||||
QList<AircraftItemPtr> variants;
|
||||
bool usesHeliports = false;
|
||||
bool usesSeaports = false;
|
||||
QList<QUrl> previews;
|
||||
bool isPrimary = false;
|
||||
QString thumbnailPath;
|
||||
QString minFGVersion;
|
||||
bool needsMaintenance = false;
|
||||
private:
|
||||
mutable QPixmap m_thumbnail;
|
||||
};
|
||||
|
||||
class AircraftItemModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -129,10 +90,6 @@ public:
|
|||
|
||||
void setPackageRoot(const simgear::pkg::RootRef& root);
|
||||
|
||||
void setPaths(QStringList paths);
|
||||
|
||||
void scanDirs();
|
||||
|
||||
int rowCount(const QModelIndex& parent) const override;
|
||||
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
|
@ -166,21 +123,6 @@ public:
|
|||
*/
|
||||
QString nameForAircraftURI(QUrl uri) const;
|
||||
|
||||
/**
|
||||
* @helper to determine if a particular path is likely to contain
|
||||
* aircraft or not. Checks for -set.xml files one level down in the tree.
|
||||
*
|
||||
*/
|
||||
static bool isCandidateAircraftPath(QString path);
|
||||
|
||||
enum AircraftStatus
|
||||
{
|
||||
AircraftOk = 0,
|
||||
AircraftUnmaintained,
|
||||
AircraftNeedsNewerSimulator,
|
||||
AircraftNeedsOlderSimulator // won't ever occur for the moment
|
||||
};
|
||||
|
||||
int aircraftNeedingUpdated() const;
|
||||
|
||||
bool showUpdateAll() const;
|
||||
|
@ -190,17 +132,14 @@ signals:
|
|||
|
||||
void aircraftInstallCompleted(QModelIndex index);
|
||||
|
||||
void scanCompleted();
|
||||
|
||||
void aircraftNeedingUpdatedChanged();
|
||||
|
||||
public slots:
|
||||
void setShowUpdateAll(bool showUpdateAll);
|
||||
|
||||
private slots:
|
||||
void onScanResults();
|
||||
|
||||
void onScanFinished();
|
||||
void onScanStarted();
|
||||
void onScanAddedItems(int count);
|
||||
|
||||
private:
|
||||
friend class PackageDelegate;
|
||||
|
@ -212,11 +151,9 @@ private:
|
|||
struct DelegateState
|
||||
{
|
||||
quint32 variant = 0;
|
||||
quint32 thumbnail = 0;
|
||||
};
|
||||
|
||||
QVariant dataFromItem(AircraftItemPtr item, const DelegateState& state, int role) const;
|
||||
QVariant itemAircraftStatus(AircraftItemPtr item, const DelegateState& ds) const;
|
||||
|
||||
QVariant dataFromPackage(const simgear::pkg::PackageRef& item,
|
||||
const DelegateState& state, int role) const;
|
||||
|
@ -225,17 +162,12 @@ private:
|
|||
const DelegateState& state, bool download = true) const;
|
||||
|
||||
QVariant packagePreviews(simgear::pkg::PackageRef p, const DelegateState &ds) const;
|
||||
QVariant packageAircraftStatus(simgear::pkg::PackageRef p, const DelegateState &ds) const;
|
||||
|
||||
void abandonCurrentScan();
|
||||
void refreshPackages();
|
||||
|
||||
void installSucceeded(QModelIndex index);
|
||||
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
|
||||
|
||||
QStringList m_paths;
|
||||
AircraftScanThread* m_scanThread = nullptr;
|
||||
QVector<AircraftItemPtr> m_items;
|
||||
PackageDelegate* m_delegate = nullptr;
|
||||
|
||||
QVector<DelegateState> m_delegateStates;
|
||||
|
@ -245,6 +177,7 @@ private:
|
|||
|
||||
mutable QHash<QString, QPixmap> m_downloadedPixmapCache;
|
||||
int m_cachedUpdateCount = 0;
|
||||
int m_cachedLocalAircraftCount = 0;
|
||||
bool m_showUpdateAll = true;
|
||||
};
|
||||
|
||||
|
|
|
@ -169,6 +169,11 @@ if (HAVE_QT)
|
|||
FGQmlInstance.hxx
|
||||
FGQmlPropertyNode.cxx
|
||||
FGQmlPropertyNode.hxx
|
||||
QmlAircraftInfo.cxx
|
||||
QmlAircraftInfo.hxx
|
||||
LocalAircraftCache.cxx
|
||||
LocalAircraftCache.hxx
|
||||
|
||||
)
|
||||
|
||||
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "MPServersModel.h"
|
||||
#include "ThumbnailImageItem.hxx"
|
||||
#include "FlickableExtentQuery.hxx"
|
||||
#include "LocalAircraftCache.hxx"
|
||||
|
||||
#include "ui_Launcher.h"
|
||||
|
||||
|
@ -173,7 +174,10 @@ LauncherMainWindow::LauncherMainWindow() :
|
|||
this, &LauncherMainWindow::onAircraftInstalledCompleted);
|
||||
connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
|
||||
this, &LauncherMainWindow::onAircraftInstallFailed);
|
||||
connect(m_aircraftModel, &AircraftItemModel::scanCompleted,
|
||||
|
||||
|
||||
connect(LocalAircraftCache::instance(),
|
||||
&LocalAircraftCache::scanCompleted,
|
||||
this, &LauncherMainWindow::updateSelectedAircraft);
|
||||
|
||||
AddOnsPage* addOnsPage = new AddOnsPage(NULL, globals->packageRoot());
|
||||
|
@ -191,9 +195,9 @@ LauncherMainWindow::LauncherMainWindow() :
|
|||
this, &LauncherMainWindow::delayedAircraftModelReset);
|
||||
|
||||
QSettings settings;
|
||||
m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
LocalAircraftCache::instance()->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
LocalAircraftCache::instance()->scanDirs();
|
||||
m_aircraftModel->setPackageRoot(globals->packageRoot());
|
||||
m_aircraftModel->scanDirs();
|
||||
|
||||
buildSettingsSections();
|
||||
buildEnvironmentSections();
|
||||
|
@ -864,8 +868,10 @@ void LauncherMainWindow::downloadDirChanged(QString path)
|
|||
QSettings settings;
|
||||
// re-scan the aircraft list
|
||||
m_aircraftModel->setPackageRoot(globals->packageRoot());
|
||||
m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
m_aircraftModel->scanDirs();
|
||||
|
||||
auto aircraftCache = LocalAircraftCache::instance();
|
||||
aircraftCache->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
aircraftCache->scanDirs();
|
||||
|
||||
emit showNoOfficialHangarChanged();
|
||||
|
||||
|
@ -909,8 +915,9 @@ simgear::pkg::PackageRef LauncherMainWindow::packageForAircraftURI(QUrl uri) con
|
|||
void LauncherMainWindow::onAircraftPathsChanged()
|
||||
{
|
||||
QSettings settings;
|
||||
m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
m_aircraftModel->scanDirs();
|
||||
auto aircraftCache = LocalAircraftCache::instance();
|
||||
aircraftCache->setPaths(settings.value("aircraft-paths").toStringList());
|
||||
aircraftCache->scanDirs();
|
||||
}
|
||||
|
||||
void LauncherMainWindow::onChangeDataDir()
|
||||
|
|
519
src/GUI/LocalAircraftCache.cxx
Normal file
519
src/GUI/LocalAircraftCache.cxx
Normal file
|
@ -0,0 +1,519 @@
|
|||
// Written by James Turner, started October 2017
|
||||
//
|
||||
// Copyright (C) 2017 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "LocalAircraftCache.hxx"
|
||||
|
||||
#include <QDir>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QDataStream>
|
||||
#include <QMap>
|
||||
#include <QSettings>
|
||||
#include <QDebug>
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
#include <Include/version.h>
|
||||
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
static quint32 CACHE_VERSION = 8;
|
||||
|
||||
const int STANDARD_THUMBNAIL_HEIGHT = 128;
|
||||
const int STANDARD_THUMBNAIL_WIDTH = 172;
|
||||
|
||||
AircraftItem::AircraftItem()
|
||||
{
|
||||
}
|
||||
|
||||
AircraftItem::AircraftItem(QDir dir, QString filePath)
|
||||
{
|
||||
SGPropertyNode root;
|
||||
readProperties(filePath.toStdString(), &root);
|
||||
|
||||
if (!root.hasChild("sim")) {
|
||||
throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr sim = root.getNode("sim");
|
||||
|
||||
path = filePath;
|
||||
pathModTime = QFileInfo(path).lastModified();
|
||||
if (sim->getBoolValue("exclude-from-gui", false)) {
|
||||
excluded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
description = sim->getStringValue("description");
|
||||
authors = sim->getStringValue("author");
|
||||
|
||||
if (sim->hasChild("rating")) {
|
||||
SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
|
||||
ratings[0] = ratingsNode->getIntValue("FDM");
|
||||
ratings[1] = ratingsNode->getIntValue("systems");
|
||||
ratings[2] = ratingsNode->getIntValue("cockpit");
|
||||
ratings[3] = ratingsNode->getIntValue("model");
|
||||
|
||||
}
|
||||
|
||||
if (sim->hasChild("long-description")) {
|
||||
// clean up any XML whitspace in the text.
|
||||
longDescription = QString(sim->getStringValue("long-description")).simplified();
|
||||
}
|
||||
|
||||
if (sim->hasChild("variant-of")) {
|
||||
variantOf = sim->getStringValue("variant-of");
|
||||
} else {
|
||||
isPrimary = true;
|
||||
}
|
||||
|
||||
if (sim->hasChild("primary-set")) {
|
||||
isPrimary = sim->getBoolValue("primary-set");
|
||||
}
|
||||
|
||||
if (sim->hasChild("tags")) {
|
||||
SGPropertyNode_ptr tagsNode = sim->getChild("tags");
|
||||
int nChildren = tagsNode->nChildren();
|
||||
for (int i = 0; i < nChildren; i++) {
|
||||
const SGPropertyNode* c = tagsNode->getChild(i);
|
||||
if (strcmp(c->getName(), "tag") == 0) {
|
||||
const char* tagName = c->getStringValue();
|
||||
usesHeliports |= (strcmp(tagName, "helicopter") == 0);
|
||||
// could also consider vtol tag?
|
||||
usesSeaports |= (strcmp(tagName, "seaplane") == 0);
|
||||
usesSeaports |= (strcmp(tagName, "floats") == 0);
|
||||
|
||||
needsMaintenance |= (strcmp(tagName, "needs-maintenance") == 0);
|
||||
}
|
||||
} // of tags iteration
|
||||
} // of set-xml has tags
|
||||
|
||||
if (sim->hasChild("previews")) {
|
||||
SGPropertyNode_ptr previewsNode = sim->getChild("previews");
|
||||
for (auto previewNode : previewsNode->getChildren("preview")) {
|
||||
// add file path as url
|
||||
QString pathInXml = QString::fromStdString(previewNode->getStringValue("path"));
|
||||
QString previewPath = dir.absoluteFilePath(pathInXml);
|
||||
previews.append(QUrl::fromLocalFile(previewPath));
|
||||
}
|
||||
}
|
||||
|
||||
if (sim->hasChild("thumbnail")) {
|
||||
thumbnailPath = sim->getStringValue("thumbnail");
|
||||
} else {
|
||||
thumbnailPath = "thumbnail.jpg";
|
||||
}
|
||||
|
||||
if (sim->hasChild("minimum-fg-version")) {
|
||||
minFGVersion = sim->getStringValue("minimum-fg-version");
|
||||
}
|
||||
}
|
||||
|
||||
QString AircraftItem::baseName() const
|
||||
{
|
||||
QString fn = QFileInfo(path).fileName();
|
||||
fn.truncate(fn.count() - 8);
|
||||
return fn;
|
||||
}
|
||||
|
||||
void AircraftItem::fromDataStream(QDataStream& ds)
|
||||
{
|
||||
ds >> path >> pathModTime >> excluded;
|
||||
if (excluded) {
|
||||
return;
|
||||
}
|
||||
|
||||
ds >> description >> longDescription >> authors >> variantOf >> isPrimary;
|
||||
for (int i=0; i<4; ++i) ds >> ratings[i];
|
||||
ds >> previews;
|
||||
ds >> thumbnailPath;
|
||||
ds >> minFGVersion;
|
||||
ds >> needsMaintenance >> usesHeliports >> usesSeaports;
|
||||
}
|
||||
|
||||
void AircraftItem::toDataStream(QDataStream& ds) const
|
||||
{
|
||||
ds << path << pathModTime << excluded;
|
||||
if (excluded) {
|
||||
return;
|
||||
}
|
||||
|
||||
ds << description << longDescription << authors << variantOf << isPrimary;
|
||||
for (int i=0; i<4; ++i) ds << ratings[i];
|
||||
ds << previews;
|
||||
ds << thumbnailPath;
|
||||
ds << minFGVersion;
|
||||
ds << needsMaintenance << usesHeliports << usesSeaports;
|
||||
}
|
||||
|
||||
QPixmap AircraftItem::thumbnail(bool loadIfRequired) const
|
||||
{
|
||||
if (m_thumbnail.isNull() && loadIfRequired) {
|
||||
QFileInfo info(path);
|
||||
QDir dir = info.dir();
|
||||
if (dir.exists(thumbnailPath)) {
|
||||
m_thumbnail.load(dir.filePath(thumbnailPath));
|
||||
// resize to the standard size
|
||||
if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) {
|
||||
m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT, Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_thumbnail;
|
||||
}
|
||||
|
||||
QVariant AircraftItem::status(int variant)
|
||||
{
|
||||
if (needsMaintenance) {
|
||||
return LocalAircraftCache::AircraftUnmaintained;
|
||||
}
|
||||
|
||||
if (minFGVersion.isEmpty()) {
|
||||
return LocalAircraftCache::AircraftOk;
|
||||
}
|
||||
|
||||
const int c = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION,
|
||||
minFGVersion.toStdString(), 2);
|
||||
return (c < 0) ? LocalAircraftCache::AircraftNeedsNewerSimulator
|
||||
: LocalAircraftCache::AircraftOk;
|
||||
|
||||
}
|
||||
|
||||
class AircraftScanThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AircraftScanThread(QStringList dirsToScan) :
|
||||
m_dirs(dirsToScan),
|
||||
m_done(false)
|
||||
{
|
||||
}
|
||||
|
||||
~AircraftScanThread()
|
||||
{
|
||||
}
|
||||
|
||||
/** thread-safe access to items already scanned */
|
||||
QVector<AircraftItemPtr> items()
|
||||
{
|
||||
QVector<AircraftItemPtr> result;
|
||||
QMutexLocker g(&m_lock);
|
||||
result.swap(m_items);
|
||||
g.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void setDone()
|
||||
{
|
||||
m_done = true;
|
||||
}
|
||||
Q_SIGNALS:
|
||||
void addedItems();
|
||||
|
||||
protected:
|
||||
virtual void run()
|
||||
{
|
||||
readCache();
|
||||
|
||||
Q_FOREACH(QString d, m_dirs) {
|
||||
scanAircraftDir(QDir(d));
|
||||
if (m_done) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
writeCache();
|
||||
}
|
||||
|
||||
private:
|
||||
void readCache()
|
||||
{
|
||||
QSettings settings;
|
||||
QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
|
||||
if (!cacheData.isEmpty()) {
|
||||
QDataStream ds(cacheData);
|
||||
quint32 count, cacheVersion;
|
||||
ds >> cacheVersion >> count;
|
||||
|
||||
if (cacheVersion != CACHE_VERSION) {
|
||||
return; // mis-matched cache, version, drop
|
||||
}
|
||||
|
||||
for (quint32 i=0; i<count; ++i) {
|
||||
AircraftItemPtr item(new AircraftItem);
|
||||
item->fromDataStream(ds);
|
||||
|
||||
QFileInfo finfo(item->path);
|
||||
if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
|
||||
// corresponding -set.xml file still exists and is
|
||||
// unmodified
|
||||
m_cachedItems[item->path] = item;
|
||||
}
|
||||
} // of cached item iteration
|
||||
}
|
||||
}
|
||||
|
||||
void writeCache()
|
||||
{
|
||||
QSettings settings;
|
||||
QByteArray cacheData;
|
||||
{
|
||||
QDataStream ds(&cacheData, QIODevice::WriteOnly);
|
||||
quint32 count = m_nextCache.count();
|
||||
ds << CACHE_VERSION << count;
|
||||
|
||||
Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) {
|
||||
item->toDataStream(ds);
|
||||
}
|
||||
}
|
||||
|
||||
settings.setValue("aircraft-cache", cacheData);
|
||||
}
|
||||
|
||||
void scanAircraftDir(QDir path)
|
||||
{
|
||||
QTime t;
|
||||
t.start();
|
||||
|
||||
QStringList filters;
|
||||
filters << "*-set.xml";
|
||||
Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||
QDir childDir(child.absoluteFilePath());
|
||||
QMap<QString, AircraftItemPtr> baseAircraft;
|
||||
QList<AircraftItemPtr> variants;
|
||||
|
||||
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
|
||||
try {
|
||||
QString absolutePath = xmlChild.absoluteFilePath();
|
||||
AircraftItemPtr item;
|
||||
|
||||
if (m_cachedItems.contains(absolutePath)) {
|
||||
item = m_cachedItems.value(absolutePath);
|
||||
} else {
|
||||
item = AircraftItemPtr(new AircraftItem(childDir, absolutePath));
|
||||
}
|
||||
|
||||
m_nextCache[absolutePath] = item;
|
||||
|
||||
if (item->excluded) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->isPrimary) {
|
||||
baseAircraft.insert(item->baseName(), item);
|
||||
} else {
|
||||
variants.append(item);
|
||||
}
|
||||
} catch (sg_exception& e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_done) {
|
||||
return;
|
||||
}
|
||||
} // of set.xml iteration
|
||||
|
||||
// bind variants to their principals
|
||||
Q_FOREACH(AircraftItemPtr item, variants) {
|
||||
if (!baseAircraft.contains(item->variantOf)) {
|
||||
qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
|
||||
continue;
|
||||
}
|
||||
|
||||
baseAircraft.value(item->variantOf)->variants.append(item);
|
||||
}
|
||||
|
||||
// lock mutex while we modify the items array
|
||||
{
|
||||
QMutexLocker g(&m_lock);
|
||||
m_items+=(baseAircraft.values().toVector());
|
||||
}
|
||||
|
||||
emit addedItems();
|
||||
} // of subdir iteration
|
||||
}
|
||||
|
||||
QMutex m_lock;
|
||||
QStringList m_dirs;
|
||||
QVector<AircraftItemPtr> m_items;
|
||||
|
||||
QMap<QString, AircraftItemPtr > m_cachedItems;
|
||||
QMap<QString, AircraftItemPtr > m_nextCache;
|
||||
|
||||
bool m_done;
|
||||
};
|
||||
|
||||
std::unique_ptr<LocalAircraftCache> static_cacheInstance;
|
||||
|
||||
LocalAircraftCache* LocalAircraftCache::instance()
|
||||
{
|
||||
if (!static_cacheInstance) {
|
||||
static_cacheInstance.reset(new LocalAircraftCache);
|
||||
}
|
||||
|
||||
return static_cacheInstance.get();
|
||||
}
|
||||
|
||||
LocalAircraftCache::LocalAircraftCache()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
LocalAircraftCache::~LocalAircraftCache()
|
||||
{
|
||||
abandonCurrentScan();
|
||||
|
||||
}
|
||||
|
||||
void LocalAircraftCache::setPaths(QStringList paths)
|
||||
{
|
||||
m_paths = paths;
|
||||
}
|
||||
|
||||
void LocalAircraftCache::scanDirs()
|
||||
{
|
||||
abandonCurrentScan();
|
||||
|
||||
QStringList dirs = m_paths;
|
||||
|
||||
Q_FOREACH(SGPath ap, globals->get_aircraft_paths()) {
|
||||
dirs << QString::fromStdString(ap.utf8Str());
|
||||
}
|
||||
|
||||
SGPath rootAircraft(globals->get_fg_root());
|
||||
rootAircraft.append("Aircraft");
|
||||
dirs << QString::fromStdString(rootAircraft.utf8Str());
|
||||
|
||||
m_scanThread = new AircraftScanThread(dirs);
|
||||
connect(m_scanThread, &AircraftScanThread::finished, this,
|
||||
&LocalAircraftCache::onScanFinished);
|
||||
connect(m_scanThread, &AircraftScanThread::addedItems,
|
||||
this, &LocalAircraftCache::onScanResults);
|
||||
m_scanThread->start();
|
||||
|
||||
emit scanStarted();
|
||||
}
|
||||
|
||||
int LocalAircraftCache::itemCount() const
|
||||
{
|
||||
return m_items.size();
|
||||
}
|
||||
|
||||
AircraftItemPtr LocalAircraftCache::itemAt(int index) const
|
||||
{
|
||||
return 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));
|
||||
if (item->path == path) {
|
||||
return row;
|
||||
}
|
||||
|
||||
// check variants too
|
||||
for (int vr=0; vr < item->variants.size(); ++vr) {
|
||||
if (item->variants.at(vr)->path == path) {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
QVector<AircraftItemPtr> LocalAircraftCache::newestItems(int count)
|
||||
{
|
||||
QVector<AircraftItemPtr> r;
|
||||
r.reserve(count);
|
||||
int total = m_items.size();
|
||||
for (int i = total - count; i < count; ++i) {
|
||||
r.push_back(m_items.at(i));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
|
||||
{
|
||||
int index = findIndexWithUri(aircraftUri);
|
||||
if (index >= 0) {
|
||||
return m_items.at(index);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void LocalAircraftCache::abandonCurrentScan()
|
||||
{
|
||||
if (m_scanThread) {
|
||||
m_scanThread->setDone();
|
||||
m_scanThread->wait(1000);
|
||||
delete m_scanThread;
|
||||
m_scanThread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LocalAircraftCache::onScanResults()
|
||||
{
|
||||
QVector<AircraftItemPtr> newItems = m_scanThread->items();
|
||||
if (newItems.isEmpty())
|
||||
return;
|
||||
|
||||
|
||||
m_items+=newItems;
|
||||
emit addedItems(newItems.size());
|
||||
}
|
||||
|
||||
void LocalAircraftCache::onScanFinished()
|
||||
{
|
||||
delete m_scanThread;
|
||||
m_scanThread = nullptr;
|
||||
emit scanCompleted();
|
||||
}
|
||||
|
||||
bool LocalAircraftCache::isCandidateAircraftPath(QString path)
|
||||
{
|
||||
QStringList filters;
|
||||
filters << "*-set.xml";
|
||||
int dirCount = 0,
|
||||
setXmlCount = 0;
|
||||
|
||||
QDir d(path);
|
||||
Q_FOREACH(QFileInfo child, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||
QDir childDir(child.absoluteFilePath());
|
||||
++dirCount;
|
||||
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
|
||||
++setXmlCount;
|
||||
}
|
||||
|
||||
if ((setXmlCount > 0) || (dirCount > 10)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (setXmlCount > 0);
|
||||
}
|
||||
|
||||
#include "LocalAircraftCache.moc"
|
138
src/GUI/LocalAircraftCache.hxx
Normal file
138
src/GUI/LocalAircraftCache.hxx
Normal file
|
@ -0,0 +1,138 @@
|
|||
// Written by James Turner, started October 2017
|
||||
|
||||
//
|
||||
// Copyright (C) 2017 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#ifndef LOCALAIRCRAFTCACHE_HXX
|
||||
#define LOCALAIRCRAFTCACHE_HXX
|
||||
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QDateTime>
|
||||
#include <QUrl>
|
||||
#include <QSharedPointer>
|
||||
#include <QDir>
|
||||
#include <QVariant>
|
||||
|
||||
class QDataStream;
|
||||
struct AircraftItem;
|
||||
class AircraftScanThread;
|
||||
|
||||
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
|
||||
|
||||
struct AircraftItem
|
||||
{
|
||||
AircraftItem();
|
||||
|
||||
AircraftItem(QDir dir, QString filePath);
|
||||
|
||||
// the file-name without -set.xml suffix
|
||||
QString baseName() const;
|
||||
|
||||
void fromDataStream(QDataStream& ds);
|
||||
|
||||
void toDataStream(QDataStream& ds) const;
|
||||
|
||||
QPixmap thumbnail(bool loadIfRequired = true) const;
|
||||
|
||||
bool excluded = false;
|
||||
QString path;
|
||||
QString description;
|
||||
QString longDescription;
|
||||
QString authors;
|
||||
int ratings[4] = {0, 0, 0, 0};
|
||||
QString variantOf;
|
||||
QDateTime pathModTime;
|
||||
QList<AircraftItemPtr> variants;
|
||||
bool usesHeliports = false;
|
||||
bool usesSeaports = false;
|
||||
QList<QUrl> previews;
|
||||
bool isPrimary = false;
|
||||
QString thumbnailPath;
|
||||
QString minFGVersion;
|
||||
bool needsMaintenance = false;
|
||||
|
||||
QVariant status(int variant);
|
||||
private:
|
||||
mutable QPixmap m_thumbnail;
|
||||
};
|
||||
|
||||
class LocalAircraftCache : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
~LocalAircraftCache();
|
||||
|
||||
static LocalAircraftCache* instance();
|
||||
|
||||
|
||||
void setPaths(QStringList paths);
|
||||
|
||||
void scanDirs();
|
||||
|
||||
|
||||
/**
|
||||
* @helper to determine if a particular path is likely to contain
|
||||
* aircraft or not. Checks for -set.xml files one level down in the tree.
|
||||
*
|
||||
*/
|
||||
static bool isCandidateAircraftPath(QString path);
|
||||
|
||||
int itemCount() const;
|
||||
|
||||
AircraftItemPtr itemAt(int index) const;
|
||||
|
||||
AircraftItemPtr findItemWithUri(QUrl aircraftUri) const;
|
||||
int findIndexWithUri(QUrl aircraftUri) const;
|
||||
|
||||
QVector<AircraftItemPtr> newestItems(int count);
|
||||
|
||||
QVariant aircraftStatus(AircraftItemPtr item) const;
|
||||
|
||||
enum AircraftStatus
|
||||
{
|
||||
AircraftOk = 0,
|
||||
AircraftUnmaintained,
|
||||
AircraftNeedsNewerSimulator,
|
||||
AircraftNeedsOlderSimulator // won't ever occur for the moment
|
||||
};
|
||||
|
||||
signals:
|
||||
|
||||
void scanStarted();
|
||||
void scanCompleted();
|
||||
|
||||
void addedItems(int count);
|
||||
public slots:
|
||||
|
||||
private slots:
|
||||
void onScanResults();
|
||||
|
||||
void onScanFinished();
|
||||
|
||||
private:
|
||||
explicit LocalAircraftCache();
|
||||
|
||||
void abandonCurrentScan();
|
||||
|
||||
QStringList m_paths;
|
||||
AircraftScanThread* m_scanThread = nullptr;
|
||||
QVector<AircraftItemPtr> m_items;
|
||||
|
||||
};
|
||||
|
||||
#endif // LOCALAIRCRAFTCACHE_HXX
|
|
@ -12,6 +12,7 @@
|
|||
#include "AircraftModel.hxx"
|
||||
#include "InstallSceneryDialog.hxx"
|
||||
#include "QtLauncher.hxx"
|
||||
#include "LocalAircraftCache.hxx"
|
||||
|
||||
#include <Main/options.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
|
@ -137,7 +138,7 @@ void AddOnsPage::onAddAircraftPath()
|
|||
// to check for that case and handle it gracefully.
|
||||
bool pathOk = false;
|
||||
|
||||
if (AircraftItemModel::isCandidateAircraftPath(path)) {
|
||||
if (LocalAircraftCache::isCandidateAircraftPath(path)) {
|
||||
m_ui->aircraftPathsList->addItem(path);
|
||||
pathOk = true;
|
||||
} else {
|
||||
|
@ -145,7 +146,7 @@ void AddOnsPage::onAddAircraftPath()
|
|||
QDir d(path);
|
||||
if (d.exists("Aircraft")) {
|
||||
QString p2 = d.filePath("Aircraft");
|
||||
if (AircraftItemModel::isCandidateAircraftPath(p2)) {
|
||||
if (LocalAircraftCache::isCandidateAircraftPath(p2)) {
|
||||
m_ui->aircraftPathsList->addItem(p2);
|
||||
pathOk = true;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
public slots:
|
||||
void setPath(QString path)
|
||||
{
|
||||
if (_propertyPath == path.toStdString());
|
||||
if (_propertyPath == path.toStdString())
|
||||
return;
|
||||
|
||||
_propertyPath = path.toStdString();
|
||||
|
|
224
src/GUI/QmlAircraftInfo.cxx
Normal file
224
src/GUI/QmlAircraftInfo.cxx
Normal file
|
@ -0,0 +1,224 @@
|
|||
#include "QmlAircraftInfo.hxx"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
|
||||
#include <simgear/package/Install.hxx>
|
||||
|
||||
#include <Include/version.h>
|
||||
|
||||
#include "LocalAircraftCache.hxx"
|
||||
|
||||
QmlAircraftInfo::QmlAircraftInfo(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QmlAircraftInfo::~QmlAircraftInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int QmlAircraftInfo::numPreviews() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QmlAircraftInfo::numVariants() const
|
||||
{
|
||||
if (_item) {
|
||||
return _item->variants.size();
|
||||
} else if (_package) {
|
||||
return _package->variants().size();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if (_item) {
|
||||
return resolveItem()->authors;
|
||||
} else if (_package) {
|
||||
std::string authors = _package->getLocalisedProp("author", _variant);
|
||||
return QString::fromStdString(authors);
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString QmlAircraftInfo::packageId() const
|
||||
{
|
||||
if (_package) {
|
||||
return QString::fromStdString(_package->variants()[_variant]);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int QmlAircraftInfo::packageSize() const
|
||||
{
|
||||
if (_package) {
|
||||
return _package->fileSizeBytes();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QmlAircraftInfo::downloadedBytes() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
QVariant QmlAircraftInfo::status() const
|
||||
{
|
||||
if (_item) {
|
||||
return _item->status(_variant);
|
||||
} else if (_package) {
|
||||
return packageAircraftStatus(_package);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void QmlAircraftInfo::setUri(QUrl uri)
|
||||
{
|
||||
if (_uri == uri)
|
||||
return;
|
||||
|
||||
_uri = uri;
|
||||
|
||||
|
||||
emit uriChanged();
|
||||
emit infoChanged();
|
||||
}
|
||||
|
||||
void QmlAircraftInfo::requestInstallUpdate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QmlAircraftInfo::requestUninstall()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QmlAircraftInfo::requestInstallCancel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
94
src/GUI/QmlAircraftInfo.hxx
Normal file
94
src/GUI/QmlAircraftInfo.hxx
Normal file
|
@ -0,0 +1,94 @@
|
|||
#ifndef QMLAIRCRAFTINFO_HXX
|
||||
#define QMLAIRCRAFTINFO_HXX
|
||||
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <simgear/package/Catalog.hxx>
|
||||
#include <simgear/package/Package.hxx>
|
||||
|
||||
struct AircraftItem;
|
||||
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
|
||||
|
||||
class QmlAircraftInfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QUrl uri READ uri WRITE setUri NOTIFY uriChanged)
|
||||
|
||||
Q_PROPERTY(int numPreviews READ numPreviews NOTIFY infoChanged)
|
||||
Q_PROPERTY(int numVariants READ numVariants NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(QString name READ name NOTIFY infoChanged)
|
||||
Q_PROPERTY(QString description READ description NOTIFY infoChanged)
|
||||
Q_PROPERTY(QString authors READ authors NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(QUrl thumbnail READ thumbnail NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(QString pathOnDisk READ pathOnDisk NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(QString packageId READ packageId NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(int packageSize READ packageSize NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(int downloadedBytes READ downloadedBytes NOTIFY downloadChanged)
|
||||
|
||||
Q_PROPERTY(QVariant status READ status NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(QString minimumFGVersion READ minimumFGVersion NOTIFY infoChanged)
|
||||
|
||||
Q_INVOKABLE void requestInstallUpdate();
|
||||
|
||||
Q_INVOKABLE void requestUninstall();
|
||||
|
||||
Q_INVOKABLE void requestInstallCancel();
|
||||
|
||||
Q_PROPERTY(QVariantList ratings READ ratings NOTIFY infoChanged)
|
||||
|
||||
public:
|
||||
explicit QmlAircraftInfo(QObject *parent = nullptr);
|
||||
virtual ~QmlAircraftInfo();
|
||||
|
||||
QUrl uri() const
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
|
||||
int numPreviews() const;
|
||||
int numVariants() const;
|
||||
|
||||
QString name() const;
|
||||
QString description() const;
|
||||
QString authors() const;
|
||||
QVariantList ratings() const;
|
||||
|
||||
QUrl thumbnail() const;
|
||||
QString pathOnDisk() const;
|
||||
|
||||
QString packageId() const;
|
||||
int packageSize() const;
|
||||
int downloadedBytes() const;
|
||||
|
||||
QVariant status() const;
|
||||
QString minimumFGVersion() const;
|
||||
|
||||
static QVariant packageAircraftStatus(simgear::pkg::PackageRef p);
|
||||
signals:
|
||||
void uriChanged();
|
||||
void infoChanged();
|
||||
void downloadChanged();
|
||||
public slots:
|
||||
|
||||
void setUri(QUrl uri);
|
||||
|
||||
private:
|
||||
QUrl _uri;
|
||||
simgear::pkg::PackageRef _package;
|
||||
AircraftItemPtr _item;
|
||||
int _variant = 0;
|
||||
|
||||
AircraftItemPtr resolveItem() const;
|
||||
};
|
||||
|
||||
#endif // QMLAIRCRAFTINFO_HXX
|
|
@ -35,6 +35,7 @@
|
|||
<file>Button.qml</file>
|
||||
<file>AircraftWarningPanel.qml</file>
|
||||
<file>Scrollbar.qml</file>
|
||||
<file>AircraftDetailsView.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/preview">
|
||||
<file alias="close-icon">preview-close.png</file>
|
||||
|
|
Loading…
Add table
Reference in a new issue