1
0
Fork 0

Lots of work on aircraft package support

This commit is contained in:
James Turner 2015-08-03 15:53:56 -05:00
parent c2cbb36d16
commit 18a898f5f9
12 changed files with 621 additions and 137 deletions

View file

@ -88,7 +88,7 @@ void AddCatalogDialog::updateUi()
"%2 aircraft are included in this hangar.").arg(catDesc).arg(m_result->packages().size());
ui->resultsSummaryLabel->setText(s);
} else if (m_state == STATE_DOWNLOAD_FAILED) {
Delegate::FailureCode code = m_result->status();
Delegate::StatusCode code = m_result->status();
qWarning() << Q_FUNC_INFO << "failed with code" << code;
QString s;
switch (code) {
@ -98,7 +98,7 @@ void AddCatalogDialog::updateUi()
break;
case Delegate::FAIL_VERSION:
s = tr("The provided hangar is for a different version of FLightGear. "
s = tr("The provided hangar is for a different version of FlightGear. "
"(This is version %1)").arg(QString::fromUtf8(FLIGHTGEAR_VERSION));
break;
@ -152,14 +152,14 @@ void AddCatalogDialog::reject()
void AddCatalogDialog::onCatalogStatusChanged(Catalog* cat)
{
Delegate::FailureCode s = cat->status();
Delegate::StatusCode s = cat->status();
qDebug() << Q_FUNC_INFO << "cat status:" << s;
switch (s) {
case Delegate::CATALOG_REFRESHED:
case Delegate::STATUS_REFRESHED:
m_state = STATE_FINISHED;
break;
case Delegate::FAIL_IN_PROGRESS:
case Delegate::STATUS_IN_PROGRESS:
// don't jump to STATE_FINISHED
return;

View file

@ -143,29 +143,79 @@ void AircraftItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem
QVariant v = index.data(AircraftPackageStatusRole);
AircraftItemStatus status = static_cast<AircraftItemStatus>(v.toInt());
// status = PackageNotInstalled;
double downloadFraction = 0.0;
if (status != PackageInstalled) {
QString buttonText, infoText;
QColor buttonColor(27, 122, 211);
double sizeInMBytes = index.data(AircraftPackageSizeRole).toInt();
sizeInMBytes /= 0x100000;
if (status == PackageDownloading) {
buttonText = tr("Cancel");
double downloadedMB = index.data(AircraftInstallDownloadedSizeRole).toInt();
downloadedMB /= 0x100000;
infoText = QStringLiteral("%1 MB of %2 MB").arg(downloadedMB, 0, 'f', 1).arg(sizeInMBytes, 0, 'f', 1);
buttonColor = QColor(0xcf, 0xcf, 0xcf);
downloadFraction = downloadedMB / sizeInMBytes;
} else if (status == PackageQueued) {
buttonText = tr("Cancel");
infoText = tr("Waiting to download %1 MB").arg(sizeInMBytes, 0, 'f', 1);
buttonColor = QColor(0xcf, 0xcf, 0xcf);
} else {
infoText = QStringLiteral("%1MB").arg(sizeInMBytes, 0, 'f', 1);
if (status == PackageNotInstalled) {
buttonText = "Install";
} else if (status == PackageUpdateAvailable) {
buttonText = "Update";
}
}
painter->setBrush(Qt::NoBrush);
QRect buttonRect = packageButtonRect(option.rect, index);
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(27, 122, 211));
painter->setBrush(buttonColor);
painter->drawRoundedRect(buttonRect, 5, 5);
painter->setPen(Qt::white);
painter->drawText(buttonRect, Qt::AlignCenter, buttonText);
QRect infoTextRect = buttonRect;
infoTextRect.setLeft(buttonRect.right() + MARGIN);
infoTextRect.setWidth(200);
if (status == PackageDownloading) {
QRect progressRect = infoTextRect;
progressRect.setHeight(6);
painter->setPen(QPen(QColor(0xcf, 0xcf, 0xcf), 0));
painter->setBrush(Qt::NoBrush);
painter->drawRoundedRect(progressRect, 3, 3);
infoTextRect.setTop(progressRect.bottom() + 1);
if (status == PackageNotInstalled) {
painter->drawText(buttonRect, Qt::AlignCenter, "Install");
} else if (status == PackageUpdateAvailable) {
painter->drawText(buttonRect, Qt::AlignCenter, "Update");
QRect progressBarRect = progressRect.marginsRemoved(QMargins(2, 2, 2, 2));
progressBarRect.setWidth(static_cast<int>(progressBarRect.width() * downloadFraction));
painter->setBrush(QColor(27, 122, 211));
painter->setPen(Qt::NoPen);
painter->drawRoundedRect(progressBarRect, 2, 2);
}
}
painter->setPen(Qt::black);
painter->drawText(infoTextRect, Qt::AlignLeft | Qt::AlignVCenter, infoText);
} // of update / install / download status
}
QSize AircraftItemDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QRect contentRect = option.rect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
const int THUMBNAIL_WIDTH = 172;
// don't request the thumbnail here for remote sources. Assume the default
//QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
//contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
contentRect.setLeft(contentRect.left() + MARGIN + THUMBNAIL_WIDTH);
QFont f;
f.setPointSize(18);
QFontMetrics metrics(f);
@ -209,10 +259,10 @@ bool AircraftItemDelegate::eventFilter( QObject*, QEvent* event )
QModelIndex index = m_view->indexAt( me->pos() );
int variantCount = index.data(AircraftVariantCountRole).toInt();
int variantIndex = index.data(AircraftVariantRole).toInt();
QRect vr = m_view->visualRect(index);
if ( (event->type() == QEvent::MouseButtonRelease) && (variantCount > 0) )
{
QRect vr = m_view->visualRect(index);
QRect leftCycleRect = leftCycleArrowRect(vr, index),
rightCycleRect = rightCycleArrowRect(vr, index);
@ -226,6 +276,22 @@ bool AircraftItemDelegate::eventFilter( QObject*, QEvent* event )
return true;
}
}
if ((event->type() == QEvent::MouseButtonRelease) &&
packageButtonRect(vr, index).contains(me->pos()))
{
QVariant v = index.data(AircraftPackageStatusRole);
AircraftItemStatus status = static_cast<AircraftItemStatus>(v.toInt());
if (status == PackageNotInstalled) {
emit requestInstall(index);
} else if ((status == PackageDownloading) || (status == PackageQueued)) {
emit cancelDownload(index);
} else if (status == PackageUpdateAvailable) {
emit requestInstall(index);
}
return true;
}
} else if ( event->type() == QEvent::MouseMove ) {
QMouseEvent* me = static_cast< QMouseEvent* >( event );
QModelIndex index = m_view->indexAt( me->pos() );
@ -273,7 +339,8 @@ QRect AircraftItemDelegate::packageButtonRect(const QRect& visualRect, const QMo
QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
return QRect(contentRect.left() + ARROW_SIZE, contentRect.bottom() - 24, 60, BUTTON_HEIGHT);
return QRect(contentRect.left() + ARROW_SIZE, contentRect.bottom() - 24,
BUTTON_WIDTH, BUTTON_HEIGHT);
}
void AircraftItemDelegate::drawRating(QPainter* painter, QString label, const QRect& box, int value) const

View file

@ -32,7 +32,8 @@ public:
static const int MARGIN = 4;
static const int ARROW_SIZE = 20;
static const int BUTTON_HEIGHT = 24;
static const int BUTTON_WIDTH = 80;
AircraftItemDelegate(QListView* view);
virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
@ -44,6 +45,9 @@ public:
Q_SIGNALS:
void variantChanged(const QModelIndex& index);
void requestInstall(const QModelIndex& index);
void cancelDownload(const QModelIndex& index);
private:
QRect leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const;
QRect rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const;

View file

@ -27,6 +27,7 @@
#include <QDataStream>
#include <QSettings>
#include <QDebug>
#include <QSharedPointer>
// Simgear
#include <simgear/props/props_io.hxx>
@ -39,6 +40,8 @@
// FlightGear
#include <Main/globals.hxx>
const int STANDARD_THUMBNAIL_HEIGHT = 128;
using namespace simgear::pkg;
AircraftItem::AircraftItem() :
@ -123,8 +126,8 @@ QPixmap AircraftItem::thumbnail() const
if (dir.exists("thumbnail.jpg")) {
m_thumbnail.load(dir.filePath("thumbnail.jpg"));
// resize to the standard size
if (m_thumbnail.height() > 128) {
m_thumbnail = m_thumbnail.scaledToHeight(128);
if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) {
m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT);
}
}
}
@ -150,9 +153,9 @@ public:
}
/** thread-safe access to items already scanned */
QList<AircraftItem*> items()
QVector<AircraftItemPtr> items()
{
QList<AircraftItem*> result;
QVector<AircraftItemPtr> result;
QMutexLocker g(&m_lock);
result.swap(m_items);
g.unlock();
@ -196,13 +199,11 @@ private:
}
for (int i=0; i<count; ++i) {
AircraftItem* item = new AircraftItem;
AircraftItemPtr item(new AircraftItem);
item->fromDataStream(ds);
QFileInfo finfo(item->path);
if (!finfo.exists() || (finfo.lastModified() != item->pathModTime)) {
delete item;
} else {
if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
// corresponding -set.xml file still exists and is
// unmodified
m_cachedItems[item->path] = item;
@ -220,7 +221,7 @@ private:
quint32 count = m_nextCache.count();
ds << CACHE_VERSION << count;
Q_FOREACH(AircraftItem* item, m_nextCache.values()) {
Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) {
item->toDataStream(ds);
}
}
@ -237,18 +238,18 @@ private:
filters << "*-set.xml";
Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QDir childDir(child.absoluteFilePath());
QMap<QString, AircraftItem*> baseAircraft;
QList<AircraftItem*> variants;
QMap<QString, AircraftItemPtr> baseAircraft;
QList<AircraftItemPtr> variants;
Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
try {
QString absolutePath = xmlChild.absoluteFilePath();
AircraftItem* item = NULL;
AircraftItemPtr item;
if (m_cachedItems.contains(absolutePath)) {
item = m_cachedItems.value(absolutePath);
} else {
item = new AircraftItem(childDir, absolutePath);
item = AircraftItemPtr(new AircraftItem(childDir, absolutePath));
}
m_nextCache[absolutePath] = item;
@ -272,10 +273,9 @@ private:
} // of set.xml iteration
// bind variants to their principals
Q_FOREACH(AircraftItem* item, variants) {
Q_FOREACH(AircraftItemPtr item, variants) {
if (!baseAircraft.contains(item->variantOf)) {
qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
delete item;
continue;
}
@ -285,7 +285,7 @@ private:
// lock mutex while we modify the items array
{
QMutexLocker g(&m_lock);
m_items.append(baseAircraft.values());
m_items.append(baseAircraft.values().toVector());
}
emit addedItems();
@ -294,19 +294,122 @@ private:
QMutex m_lock;
QStringList m_dirs;
QList<AircraftItem*> m_items;
QVector<AircraftItemPtr> m_items;
QMap<QString, AircraftItem* > m_cachedItems;
QMap<QString, AircraftItem* > m_nextCache;
QMap<QString, AircraftItemPtr > m_cachedItems;
QMap<QString, AircraftItemPtr > m_nextCache;
bool m_done;
};
class PackageDelegate : public simgear::pkg::Delegate
{
public:
PackageDelegate(AircraftItemModel* model) :
m_model(model)
{
m_model->m_packageRoot->addDelegate(this);
}
~PackageDelegate()
{
m_model->m_packageRoot->removeDelegate(this);
}
protected:
virtual void catalogRefreshed(CatalogRef aCatalog, StatusCode aReason)
{
if (aReason == STATUS_IN_PROGRESS) {
qDebug() << "doing refresh of" << QString::fromStdString(aCatalog->url());
} else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) {
m_model->refreshPackages();
} else {
qWarning() << "failed refresh of "
<< QString::fromStdString(aCatalog->url()) << ":" << aReason << endl;
}
}
virtual void startInstall(InstallRef aInstall)
{
QModelIndex mi(indexForPackage(aInstall->package()));
m_model->dataChanged(mi, mi);
}
virtual void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total)
{
Q_UNUSED(bytes);
Q_UNUSED(total);
QModelIndex mi(indexForPackage(aInstall->package()));
m_model->dataChanged(mi, mi);
}
virtual void finishInstall(InstallRef aInstall, StatusCode aReason)
{
QModelIndex mi(indexForPackage(aInstall->package()));
m_model->dataChanged(mi, mi);
if ((aReason != USER_CANCELLED) && (aReason != STATUS_SUCCESS)) {
m_model->installFailed(mi, aReason);
}
if (aReason == STATUS_SUCCESS) {
m_model->installSucceeded(mi);
}
}
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
size_t length, const uint8_t* bytes)
{
QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes), length));
if (img.isNull()) {
qWarning() << "failed to load image data for URL:" <<
QString::fromStdString(aThumbnailUrl);
return;
}
m_model->m_thumbnailPixmapCache.insert(QString::fromStdString(aThumbnailUrl),
QPixmap::fromImage(img));
// notify any affected items. Linear scan here avoids another map/dict
// structure.
PackageList::const_iterator it;
int i = 0;
for (it=m_model->m_packages.begin(); it != m_model->m_packages.end(); ++it, ++i) {
const string_list& urls((*it)->thumbnailUrls());
string_list::const_iterator cit = std::find(urls.begin(), urls.end(), aThumbnailUrl);
if (cit != urls.end()) {
QModelIndex mi(m_model->index(i + m_model->m_items.size()));
m_model->dataChanged(mi, mi);
}
} // of packages iteration
}
private:
QModelIndex indexForPackage(const PackageRef& ref) const
{
PackageList::const_iterator it = std::find(m_model->m_packages.begin(),
m_model->m_packages.end(),
ref);
if (it == m_model->m_packages.end()) {
return QModelIndex();
}
size_t offset = it - m_model->m_packages.begin();
return m_model->index(offset + m_model->m_items.size());
}
AircraftItemModel* m_model;
};
AircraftItemModel::AircraftItemModel(QObject* pr, simgear::pkg::RootRef& rootRef) :
QAbstractListModel(pr),
m_scanThread(NULL),
m_packageRoot(rootRef)
{
new PackageDelegate(this);
// packages may already be refreshed, so pull now
refreshPackages();
}
AircraftItemModel::~AircraftItemModel()
@ -324,7 +427,6 @@ void AircraftItemModel::scanDirs()
abandonCurrentScan();
beginResetModel();
qDeleteAll(m_items);
m_items.clear();
m_activeVariant.clear();
endResetModel();
@ -345,7 +447,6 @@ void AircraftItemModel::scanDirs()
connect(m_scanThread, &AircraftScanThread::addedItems,
this, &AircraftItemModel::onScanResults);
m_scanThread->start();
}
void AircraftItemModel::abandonCurrentScan()
@ -358,18 +459,51 @@ void AircraftItemModel::abandonCurrentScan()
}
}
QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
void AircraftItemModel::refreshPackages()
{
if (role == AircraftVariantRole) {
return m_activeVariant.at(index.row());
}
const AircraftItem* item(m_items.at(index.row()));
quint32 variantIndex = m_activeVariant.at(index.row());
return dataFromItem(item, variantIndex, role);
beginResetModel();
m_packages = m_packageRoot->allPackages();
m_packageVariant.resize(m_packages.size());
endResetModel();
}
QVariant AircraftItemModel::dataFromItem(const AircraftItem* item, quint32 variantIndex, int role) const
int AircraftItemModel::rowCount(const QModelIndex& parent) const
{
return m_items.size() + m_packages.size();
}
QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
{
if (index.row() >= m_items.size()) {
quint32 packageIndex = index.row() - m_items.size();
if (role == AircraftVariantRole) {
return m_packageVariant.at(packageIndex);
}
const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall();
if (role == AircraftInstallPercentRole) {
return ex.valid() ? ex->downloadedPercent() : 0;
} else if (role == AircraftInstallDownloadedSizeRole) {
return static_cast<quint64>(ex.valid() ? ex->downloadedBytes() : 0);
}
quint32 variantIndex = m_packageVariant.at(packageIndex);
return dataFromPackage(pkg, variantIndex, role);
} else {
if (role == AircraftVariantRole) {
return m_activeVariant.at(index.row());
}
quint32 variantIndex = m_activeVariant.at(index.row());
const AircraftItemPtr item(m_items.at(index.row()));
return dataFromItem(item, variantIndex, role);
}
}
QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const
{
if (role == AircraftVariantCountRole) {
return item->variants.count();
@ -410,6 +544,8 @@ QVariant AircraftItemModel::dataFromItem(const AircraftItem* item, quint32 varia
return PackageInstalled; // always the case
} else if (role == Qt::ToolTipRole) {
return item->path;
} else if (role == AircraftURIRole) {
return QUrl::fromLocalFile(item->path);
} else if (role == AircraftHasRatingsRole) {
bool have = false;
for (int i=0; i<4; ++i) {
@ -430,19 +566,28 @@ QVariant AircraftItemModel::dataFromItem(const AircraftItem* item, quint32 varia
QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 variantIndex, int role) const
{
if (role == Qt::DecorationRole) {
role = AircraftThumbnailRole; // use first thumbnail
}
if (role == Qt::DisplayRole) {
return QString::fromStdString(item->name());
} else if (role == AircraftPathRole) {
// can we return the theoretical path?
InstallRef i = item->existingInstall();
if (i.valid()) {
return QString::fromStdString(i->primarySetPath().str());
}
} else if (role == AircraftPackageIdRole) {
return QString::fromStdString(item->id());
} else if (role == AircraftPackageStatusRole) {
bool installed = item->isInstalled();
if (installed) {
InstallRef i = item->existingInstall();
InstallRef i = item->existingInstall();
if (i.valid()) {
if (i->isDownloading()) {
return PackageDownloading;
}
if (i->isQueued()) {
return PackageQueued;
}
if (i->hasUpdate()) {
return PackageUpdateAvailable;
}
@ -451,13 +596,72 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 vari
} else {
return PackageNotInstalled;
}
} else if (role >= AircraftThumbnailRole) {
return packageThumbnail(item , role - AircraftThumbnailRole);
} else if (role == AircraftAuthorsRole) {
SGPropertyNode* authors = item->properties()->getChild("author");
if (authors) {
return QString::fromStdString(authors->getStringValue());
}
} else if (role == AircraftLongDescriptionRole) {
return QString::fromStdString(item->description());
} else if (role == AircraftPackageSizeRole) {
return static_cast<int>(item->fileSizeBytes());
} else if (role == AircraftURIRole) {
return QUrl("package:" + QString::fromStdString(item->qualifiedId()));
} else if (role == AircraftHasRatingsRole) {
return item->properties()->hasChild("rating");
} else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
int ratingIndex = role - AircraftRatingRole;
SGPropertyNode* ratings = item->properties()->getChild("rating");
if (!ratings) {
return QVariant();
}
return ratings->getChild(ratingIndex)->getIntValue();
}
return QVariant();
}
QVariant AircraftItemModel::packageThumbnail(PackageRef p, int index) const
{
const string_list& thumbnails(p->thumbnailUrls());
if (index >= thumbnails.size()) {
return QVariant();
}
std::string thumbnailUrl = thumbnails.at(index);
QString urlQString(QString::fromStdString(thumbnailUrl));
if (m_thumbnailPixmapCache.contains(urlQString)) {
// cache hit, easy
return m_thumbnailPixmapCache.value(urlQString);
}
// check the on-disk store. This relies on the order of thumbnails in the
// results of thumbnailUrls and thumbnails corresponding
InstallRef ex = p->existingInstall();
if (ex.valid()) {
const string_list& thumbNames(p->thumbnails());
if (!thumbNames.empty()) {
SGPath path(ex->path());
path.append(p->thumbnails()[index]);
if (path.exists()) {
QPixmap pix;
pix.load(QString::fromStdString(path.str()));
// resize to the standard size
if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) {
pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT);
}
m_thumbnailPixmapCache[urlQString] = pix;
return pix;
}
} // of have thumbnail file names
} // of have existing install
m_packageRoot->requestThumbnailData(thumbnailUrl);
return QVariant();
}
bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == AircraftVariantRole) {
@ -469,21 +673,36 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value,
return false;
}
QModelIndex AircraftItemModel::indexOfAircraftPath(QString path) const
QModelIndex AircraftItemModel::indexOfAircraftURI(QUrl uri) const
{
for (int row=0; row <m_items.size(); ++row) {
const AircraftItem* item(m_items.at(row));
if (item->path == path) {
return index(row);
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);
}
}
} else if (uri.scheme() == "package") {
QString ident = uri.path();
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) {
for (int i=0; i < m_packages.size(); ++i) {
if (m_packages[i] == pkg) {
return index(m_items.size() + i);
}
} // of linear package scan
}
} else {
qWarning() << "Unknown aircraft URI scheme" << uri << uri.scheme();
}
return QModelIndex();
}
void AircraftItemModel::onScanResults()
{
QList<AircraftItem*> newItems = m_scanThread->items();
QVector<AircraftItemPtr> newItems = m_scanThread->items();
if (newItems.isEmpty())
return;
@ -505,4 +724,51 @@ void AircraftItemModel::onScanFinished()
m_scanThread = NULL;
}
void AircraftItemModel::installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason)
{
Q_ASSERT(index.row() >= m_items.size());
QString msg;
switch (reason) {
case Delegate::FAIL_CHECKSUM:
msg = tr("Invalid package checksum"); break;
case Delegate::FAIL_DOWNLOAD:
msg = tr("Download failed"); break;
case Delegate::FAIL_EXTRACT:
msg = tr("Package could not be extracted"); break;
case Delegate::FAIL_FILESYSTEM:
msg = tr("A local file-system error occurred"); break;
case Delegate::FAIL_UNKNOWN:
default:
msg = tr("Unknown reason");
}
quint32 packageIndex = index.row() - m_items.size();
const PackageRef& pkg(m_packages[packageIndex]);
QString packageName = QString::fromStdString(pkg->description());
emit aircraftInstallFailed(index, tr("Failed installation of package '%1': %2").arg(packageName).arg(msg));
}
void AircraftItemModel::installSucceeded(QModelIndex index)
{
qDebug() << Q_FUNC_INFO << index;
emit aircraftInstallCompleted(index);
}
bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
{
if (index.row() < m_items.size()) {
return true; // local file, always runnable
}
quint32 packageIndex = index.row() - m_items.size();
const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall();
if (!ex.valid()) {
return false; // not installed
}
return !ex->isDownloading();
}
#include "AircraftModel.moc"

View file

@ -26,6 +26,8 @@
#include <QDir>
#include <QPixmap>
#include <QStringList>
#include <QSharedPointer>
#include <QUrl>
#include <simgear/package/Root.hxx>
@ -39,6 +41,10 @@ const int AircraftPackageStatusRole = Qt::UserRole + 7;
const int AircraftPackageProgressRole = Qt::UserRole + 8;
const int AircraftLongDescriptionRole = Qt::UserRole + 9;
const int AircraftHasRatingsRole = Qt::UserRole + 10;
const int AircraftInstallPercentRole = Qt::UserRole + 11;
const int AircraftPackageSizeRole = Qt::UserRole + 12;
const int AircraftInstallDownloadedSizeRole = Qt::UserRole + 13;
const int AircraftURIRole = Qt::UserRole + 14;
const int AircraftRatingRole = Qt::UserRole + 100;
const int AircraftVariantDescriptionRole = Qt::UserRole + 200;
@ -47,6 +53,9 @@ const int AircraftThumbnailRole = Qt::UserRole + 300;
class AircraftScanThread;
class QDataStream;
struct AircraftItem;
typedef QSharedPointer<AircraftItem> AircraftItemPtr;
struct AircraftItem
{
AircraftItem();
@ -70,7 +79,7 @@ struct AircraftItem
QString variantOf;
QDateTime pathModTime;
QList<AircraftItem*> variants;
QList<AircraftItemPtr> variants;
private:
mutable QPixmap m_thumbnail;
};
@ -80,6 +89,7 @@ enum AircraftItemStatus {
PackageNotInstalled,
PackageInstalled,
PackageUpdateAvailable,
PackageQueued,
PackageDownloading
};
@ -95,11 +105,8 @@ public:
void scanDirs();
virtual int rowCount(const QModelIndex& parent) const
{
return m_items.size();
}
virtual int rowCount(const QModelIndex& parent) const;
virtual QVariant data(const QModelIndex& index, int role) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
@ -108,26 +115,53 @@ public:
* given a -set.xml path, return the corresponding model index, if one
* exists.
*/
QModelIndex indexOfAircraftPath(QString path) const;
// QModelIndex indexOfAircraftPath(QString path) const;
QModelIndex indexOfAircraftURI(QUrl uri) const;
/**
* return if a given aircraft is ready to be run, or not. Aircraft which
* are not installed, or are downloading, are not runnable.
*/
bool isIndexRunnable(const QModelIndex& index) const;
signals:
void aircraftInstallFailed(QModelIndex index, QString errorMessage);
void aircraftInstallCompleted(QModelIndex index);
private slots:
void onScanResults();
void onScanFinished();
private:
QVariant dataFromItem(const AircraftItem* item, quint32 variantIndex, int role) const;
friend class PackageDelegate;
QVariant dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const;
QVariant dataFromPackage(const simgear::pkg::PackageRef& item,
quint32 variantIndex, int role) const;
QVariant packageThumbnail(simgear::pkg::PackageRef p, int index) const;
void abandonCurrentScan();
void refreshPackages();
void installSucceeded(QModelIndex index);
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
QStringList m_paths;
AircraftScanThread* m_scanThread;
QList<AircraftItem*> m_items;
QList<quint32> m_activeVariant;
QVector<AircraftItemPtr> m_items;
QVector<quint32> m_activeVariant;
QVector<quint32> m_packageVariant;
simgear::pkg::RootRef m_packageRoot;
simgear::pkg::PackageList m_packages;
mutable QHash<QString, QPixmap> m_thumbnailPixmapCache;
};
#endif // of FG_GUI_AIRCRAFT_MODEL

View file

@ -59,7 +59,7 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
simgear::pkg::CatalogRef cat = m_packageRoot->catalogs().at(index.row());
if (role == Qt::DisplayRole) {
return QString::fromStdString(cat->description());
return QString::fromStdString(cat->description()).trimmed();
} else if (role == Qt::ToolTipRole) {
return QString::fromStdString(cat->url());
} else if (role == CatalogUrlRole) {

View file

@ -3,6 +3,7 @@
#include <QSettings>
#include <QFileDialog>
#include <QMessageBox>
#include "CatalogListModel.hxx"
#include "AddCatalogDialog.hxx"
@ -128,7 +129,17 @@ void PathsDialog::onAddCatalog()
void PathsDialog::onRemoveCatalog()
{
QModelIndex mi = m_ui->catalogsList->currentIndex();
if (mi.isValid()) {
QMessageBox mb;
mb.setText(QStringLiteral("Remove aircraft hangar '%1'?").arg(mi.data(Qt::DisplayRole).toString()));
mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
mb.setDefaultButton(QMessageBox::No);
mb.exec();
QString pkgId = mi.data(CatalogIdRole).toString();
m_packageRoot->removeCatalogById(pkgId.toStdString());
}
}
void PathsDialog::onChangeDownloadDir()

View file

@ -48,6 +48,9 @@
#include <simgear/structure/exception.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/package/Install.hxx>
#include "ui_Launcher.h"
#include "EditRatingsFilterDialog.hxx"
@ -65,6 +68,7 @@
#include <Network/HTTPClient.hxx>
using namespace flightgear;
using namespace simgear::pkg;
const int MAX_RECENT_AIRPORTS = 32;
const int MAX_RECENT_AIRCRAFT = 20;
@ -457,8 +461,6 @@ QtLauncher::QtLauncher() :
connect(m_ui->aircraftHistory, &QPushButton::clicked,
this, &QtLauncher::onPopupAircraftHistory);
restoreSettings();
QAction* qa = new QAction(this);
qa->setShortcut(QKeySequence("Ctrl+Q"));
connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
@ -495,7 +497,7 @@ QtLauncher::QtLauncher() :
updateSettingsSummary();
fgInitPackageRoot();
simgear::pkg::RootRef r(globals->packageRoot());
RootRef r(globals->packageRoot());
FGHTTPClient* http = new FGHTTPClient;
globals->add_subsystem("http", http);
@ -521,10 +523,21 @@ QtLauncher::QtLauncher() :
this, &QtLauncher::onAircraftSelected);
connect(delegate, &AircraftItemDelegate::variantChanged,
this, &QtLauncher::onAircraftSelected);
connect(delegate, &AircraftItemDelegate::requestInstall,
this, &QtLauncher::onRequestPackageInstall);
connect(delegate, &AircraftItemDelegate::cancelDownload,
this, &QtLauncher::onCancelDownload);
connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
this, &QtLauncher::onAircraftInstalledCompleted);
connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
this, &QtLauncher::onAircraftInstallFailed);
connect(m_ui->pathsButton, &QPushButton::clicked,
this, &QtLauncher::onEditPaths);
restoreSettings();
QSettings settings;
m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
m_aircraftModel->scanDirs();
@ -566,6 +579,7 @@ void QtLauncher::initApp(int& argc, char** argv)
bool QtLauncher::runLauncherDialog()
{
sglog().setLogLevels( SG_ALL, SG_INFO );
Q_INIT_RESOURCE(resources);
// startup the nav-cache now. This pre-empts normal startup of
@ -598,7 +612,7 @@ void QtLauncher::restoreSettings()
m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt());
// full paths to -set.xml files
m_recentAircraft = settings.value("recent-aircraft").toStringList();
m_recentAircraft = QUrl::fromStringList(settings.value("recent-aircraft").toStringList());
if (!m_recentAircraft.empty()) {
m_selectedAircraft = m_recentAircraft.front();
@ -638,7 +652,7 @@ void QtLauncher::saveSettings()
settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
settings.setValue("recent-aircraft", m_recentAircraft);
settings.setValue("recent-aircraft", QUrl::toStringList(m_recentAircraft));
settings.setValue("recent-airports", m_recentAirports);
settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
settings.setValue("season", m_ui->seasonCombo->currentIndex());
@ -679,13 +693,22 @@ void QtLauncher::onRun()
// aircraft
if (!m_selectedAircraft.isEmpty()) {
QFileInfo setFileInfo(m_selectedAircraft);
opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
QString setFile = setFileInfo.fileName();
Q_ASSERT(setFile.endsWith("-set.xml"));
setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
opt->addOption("aircraft", setFile.toStdString());
if (m_selectedAircraft.isLocalFile()) {
QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
QString setFile = setFileInfo.fileName();
Q_ASSERT(setFile.endsWith("-set.xml"));
setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
opt->addOption("aircraft", setFile.toStdString());
} else if (m_selectedAircraft.scheme() == "package") {
PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
// no need to set aircraft-dir, handled by the corresponding code
// in fgInitAircraft
opt->addOption("aircraft", pkg->qualifiedId());
} else {
qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
}
// manage aircraft history
if (m_recentAircraft.contains(m_selectedAircraft))
m_recentAircraft.removeOne(m_selectedAircraft);
@ -902,6 +925,29 @@ void QtLauncher::onToggleTerrasync(bool enabled)
} // of is enabled
}
void QtLauncher::onAircraftInstalledCompleted(QModelIndex index)
{
qDebug() << Q_FUNC_INFO;
QUrl u = index.data(AircraftURIRole).toUrl();
if (u == m_selectedAircraft) {
// potentially enable the run button now!
updateSelectedAircraft();
qDebug() << "updating selected aircraft" << index.data();
}
}
void QtLauncher::onAircraftInstallFailed(QModelIndex index, QString errorMessage)
{
qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage;
QMessageBox msg;
msg.setWindowTitle(tr("Aircraft insallation failed"));
msg.setText(tr("An error occurred installing the aircraft %1: %2").
arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage));
msg.addButton(QMessageBox::Ok);
msg.exec();
}
void QtLauncher::updateAirportDescription()
{
if (!m_selectedAirport) {
@ -944,20 +990,42 @@ void QtLauncher::onAirportChoiceSelected(const QModelIndex& index)
void QtLauncher::onAircraftSelected(const QModelIndex& index)
{
m_selectedAircraft = index.data(AircraftPathRole).toString();
m_selectedAircraft = index.data(AircraftURIRole).toUrl();
updateSelectedAircraft();
}
void QtLauncher::onRequestPackageInstall(const QModelIndex& index)
{
QString pkg = index.data(AircraftPackageIdRole).toString();
qDebug() << "request install of" << pkg;
simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
pref->install();
}
void QtLauncher::onCancelDownload(const QModelIndex& index)
{
QString pkg = index.data(AircraftPackageIdRole).toString();
qDebug() << "cancel download of" << pkg;
simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
simgear::pkg::InstallRef i = pref->existingInstall();
i->cancelDownload();
}
void QtLauncher::updateSelectedAircraft()
{
try {
QFileInfo info(m_selectedAircraft);
AircraftItem item(info.dir(), m_selectedAircraft);
m_ui->thumbnail->setPixmap(item.thumbnail());
m_ui->aircraftDescription->setText(item.description);
} catch (sg_exception& e) {
QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
if (index.isValid()) {
QPixmap pm = index.data(Qt::DecorationRole).value<QPixmap>();
m_ui->thumbnail->setPixmap(pm);
m_ui->aircraftDescription->setText(index.data(Qt::DisplayRole).toString());
int status = index.data(AircraftPackageStatusRole).toInt();
bool canRun = (status == PackageInstalled);
m_ui->runButton->setEnabled(canRun);
} else {
m_ui->thumbnail->setPixmap(QPixmap());
m_ui->aircraftDescription->setText("");
m_ui->runButton->setEnabled(false);
}
}
@ -985,16 +1053,16 @@ void QtLauncher::onPopupAirportHistory()
}
}
QModelIndex QtLauncher::proxyIndexForAircraftPath(QString path) const
QModelIndex QtLauncher::proxyIndexForAircraftURI(QUrl uri) const
{
return m_aircraftProxy->mapFromSource(sourceIndexForAircraftPath(path));
return m_aircraftProxy->mapFromSource(sourceIndexForAircraftURI(uri));
}
QModelIndex QtLauncher::sourceIndexForAircraftPath(QString path) const
QModelIndex QtLauncher::sourceIndexForAircraftURI(QUrl uri) const
{
AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
Q_ASSERT(sourceModel);
return sourceModel->indexOfAircraftPath(path);
return sourceModel->indexOfAircraftURI(uri);
}
void QtLauncher::onPopupAircraftHistory()
@ -1004,21 +1072,21 @@ void QtLauncher::onPopupAircraftHistory()
}
QMenu m;
Q_FOREACH(QString path, m_recentAircraft) {
QModelIndex index = sourceIndexForAircraftPath(path);
Q_FOREACH(QUrl uri, m_recentAircraft) {
QModelIndex index = sourceIndexForAircraftURI(uri);
if (!index.isValid()) {
// not scanned yet
continue;
}
QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
act->setData(path);
act->setData(uri);
}
QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
QAction* triggered = m.exec(popupPos);
if (triggered) {
m_selectedAircraft = triggered->data().toString();
QModelIndex index = proxyIndexForAircraftPath(m_selectedAircraft);
m_selectedAircraft = triggered->data().toUrl();
QModelIndex index = proxyIndexForAircraftURI(m_selectedAircraft);
m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
QItemSelectionModel::ClearAndSelect);
m_ui->aircraftFilter->clear();
@ -1129,5 +1197,17 @@ void QtLauncher::onEditPaths()
}
}
simgear::pkg::PackageRef QtLauncher::packageForAircraftURI(QUrl uri) const
{
if (uri.scheme() != "package") {
qWarning() << "invalid URL scheme:" << uri;
return simgear::pkg::PackageRef();
}
QString ident = uri.path();
qDebug() << Q_FUNC_INFO << uri << ident;
return globals->packageRoot()->getPackageById(ident.toStdString());
}
#include "QtLauncher.moc"

View file

@ -26,8 +26,12 @@
#include <QStringList>
#include <QModelIndex>
#include <QTimer>
#include <QUrl>
#include <Airports/airport.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/package/Catalog.hxx>
namespace Ui
{
@ -62,7 +66,9 @@ private slots:
void onAirportChoiceSelected(const QModelIndex& index);
void onAircraftSelected(const QModelIndex& index);
void onRequestPackageInstall(const QModelIndex& index);
void onCancelDownload(const QModelIndex& index);
void onPopupAirportHistory();
void onPopupAircraftHistory();
@ -81,6 +87,9 @@ private slots:
void onEditPaths();
void onAirportDiagramClicked(FGRunwayRef rwy);
void onAircraftInstalledCompleted(QModelIndex index);
void onAircraftInstallFailed(QModelIndex index, QString errorMessage);
private:
void setAirport(FGAirportRef ref);
void updateSelectedAircraft();
@ -88,20 +97,22 @@ private:
void restoreSettings();
void saveSettings();
QModelIndex proxyIndexForAircraftPath(QString path) const;
QModelIndex sourceIndexForAircraftPath(QString path) const;
QModelIndex proxyIndexForAircraftURI(QUrl uri) const;
QModelIndex sourceIndexForAircraftURI(QUrl uri) const;
void setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const;
simgear::pkg::PackageRef packageForAircraftURI(QUrl uri) const;
QScopedPointer<Ui::Launcher> m_ui;
AirportSearchModel* m_airportsModel;
AircraftProxyModel* m_aircraftProxy;
AircraftItemModel* m_aircraftModel;
FGAirportRef m_selectedAirport;
QString m_selectedAircraft;
QStringList m_recentAircraft,
m_recentAirports;
QUrl m_selectedAircraft;
QList<QUrl> m_recentAircraft;
QStringList m_recentAirports;
QTimer* m_subsystemIdleTimer;
int m_ratingFilters[4];

View file

@ -546,11 +546,9 @@ int fgInitAircraft(bool reinit)
// code in FindAndCacheAircraft works as normal
// note since we may be using a variant, we can't use the package ID
size_t lastDot = aircraftId.rfind('.');
if (lastDot != std::string::npos) {
aircraftId = aircraftId.substr(lastDot + 1);
aircraftProp->setStringValue(aircraftId);
}
assert(lastDot != std::string::npos);
aircraftId = aircraftId.substr(lastDot + 1);
aircraftProp->setStringValue(aircraftId);
// run the traditional-code path below
} else {
#if 0

View file

@ -47,9 +47,8 @@ typedef nasal::Ghost<pkg::PackageRef> NasalPackage;
typedef nasal::Ghost<pkg::CatalogRef> NasalCatalog;
typedef nasal::Ghost<pkg::InstallRef> NasalInstall;
namespace {
class FGDelegate : public pkg::Delegate
class FGHTTPClient::FGDelegate : public pkg::Delegate
{
public:
virtual void refreshComplete()
@ -67,48 +66,52 @@ public:
r->scheduleToUpdate((*it)->install());
}
}
virtual void failedRefresh(pkg::Catalog* aCat, FailureCode aReason)
virtual void catalogRefreshed(pkg::CatalogRef aCat, StatusCode aReason)
{
if (aCat.ptr() == NULL) {
SG_LOG(SG_IO, SG_INFO, "refresh of all catalogs done");
return;
}
switch (aReason) {
case pkg::Delegate::FAIL_SUCCESS:
SG_LOG(SG_IO, SG_WARN, "refresh of Catalog done");
case pkg::Delegate::STATUS_SUCCESS:
case pkg::Delegate::STATUS_REFRESHED:
SG_LOG(SG_IO, SG_INFO, "refresh of Catalog done:" << aCat->url());
break;
case pkg::Delegate::STATUS_IN_PROGRESS:
SG_LOG(SG_IO, SG_INFO, "refresh of Catalog started:" << aCat->url());
break;
default:
SG_LOG(SG_IO, SG_WARN, "refresh of Catalog " << aCat->url() << " failed:" << aReason);
}
}
virtual void startInstall(pkg::Install* aInstall)
virtual void startInstall(pkg::InstallRef aInstall)
{
SG_LOG(SG_IO, SG_INFO, "beginning install of:" << aInstall->package()->id()
<< " to local path:" << aInstall->path());
}
virtual void installProgress(pkg::Install* aInstall, unsigned int aBytes, unsigned int aTotal)
virtual void installProgress(pkg::InstallRef aInstall, unsigned int aBytes, unsigned int aTotal)
{
SG_LOG(SG_IO, SG_INFO, "installing:" << aInstall->package()->id() << ":"
<< aBytes << " of " << aTotal);
}
virtual void finishInstall(pkg::Install* aInstall)
virtual void finishInstall(pkg::InstallRef aInstall, StatusCode aReason)
{
if (aReason == STATUS_SUCCESS) {
SG_LOG(SG_IO, SG_INFO, "finished install of:" << aInstall->package()->id()
<< " to local path:" << aInstall->path());
} else {
SG_LOG(SG_IO, SG_WARN, "install failed of:" << aInstall->package()->id()
<< " to local path:" << aInstall->path());
}
}
virtual void failedInstall(pkg::Install* aInstall, FailureCode aReason)
{
SG_LOG(SG_IO, SG_WARN, "install failed of:" << aInstall->package()->id()
<< " to local path:" << aInstall->path());
}
};
} // of anonymous namespace
}; // of FGHTTPClient::FGDelegate
FGHTTPClient::FGHTTPClient() :
_inited(false)
@ -142,11 +145,12 @@ void FGHTTPClient::init()
// package system needs access to the HTTP engine too
packageRoot->setHTTPClient(_http.get());
packageRoot->setDelegate(new FGDelegate);
_packageDelegate.reset(new FGDelegate);
packageRoot->addDelegate(_packageDelegate.get());
const char * defaultCatalogId = fgGetString("/sim/package-system/default-catalog/id", "org.flightgear.default" );
const char * defaultCatalogId = fgGetString("/sim/package-system/default-catalog/id", "org.flightgear.official" );
const char * defaultCatalogUrl = fgGetString("/sim/package-system/default-catalog/url",
"http://fgfs.goneabitbursar.com/pkg/" FLIGHTGEAR_VERSION "/default-catalog.xml");
"http://fgfs.goneabitbursar.com/pkg/" FLIGHTGEAR_VERSION "/catalog.xml");
// setup default catalog if not present
pkg::Catalog* defaultCatalog = packageRoot->getCatalogById( defaultCatalogId );
if (!defaultCatalog) {
@ -299,7 +303,13 @@ void FGHTTPClient::postinit()
void FGHTTPClient::shutdown()
{
_http.reset();
pkg::Root* packageRoot = globals->packageRoot();
if (packageRoot && _packageDelegate.get()) {
packageRoot->removeDelegate(_packageDelegate.get());
}
_packageDelegate.reset();
_http.reset();
}
void FGHTTPClient::update(double)

View file

@ -42,8 +42,11 @@ public:
virtual void update(double);
private:
class FGDelegate;
bool _inited;
std::auto_ptr<simgear::HTTP::Client> _http;
std::auto_ptr<FGDelegate> _packageDelegate;
};
#endif // FG_HTTP_CLIENT_HXX