"%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) {
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;
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;
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);
-
- if (status == PackageNotInstalled) {
- painter->drawText(buttonRect, Qt::AlignCenter, "Install");
- } else if (status == PackageUpdateAvailable) {
- painter->drawText(buttonRect, Qt::AlignCenter, "Update");
+ 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);
+
+ 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);
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);
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() );
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
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;
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;
#include <QDataStream>
#include <QSettings>
#include <QDebug>
+#include <QSharedPointer>
// Simgear
#include <simgear/props/props_io.hxx>
// FlightGear
#include <Main/globals.hxx>
+const int STANDARD_THUMBNAIL_HEIGHT = 128;
+
using namespace simgear::pkg;
AircraftItem::AircraftItem() :
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);
}
}
}
}
/** 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();
}
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;
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);
}
}
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;
} // 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;
}
// 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();
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()
abandonCurrentScan();
beginResetModel();
- qDeleteAll(m_items);
m_items.clear();
m_activeVariant.clear();
endResetModel();
connect(m_scanThread, &AircraftScanThread::addedItems,
this, &AircraftItemModel::onScanResults);
m_scanThread->start();
-
}
void AircraftItemModel::abandonCurrentScan()
}
}
+void AircraftItemModel::refreshPackages()
+{
+ beginResetModel();
+ m_packages = m_packageRoot->allPackages();
+ m_packageVariant.resize(m_packages.size());
+ endResetModel();
+}
+
+int AircraftItemModel::rowCount(const QModelIndex& parent) const
+{
+ return m_items.size() + m_packages.size();
+}
+
QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
{
- if (role == AircraftVariantRole) {
- return m_activeVariant.at(index.row());
- }
+ 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());
+ }
- const AircraftItem* item(m_items.at(index.row()));
- quint32 variantIndex = m_activeVariant.at(index.row());
- return dataFromItem(item, variantIndex, role);
+ quint32 variantIndex = m_activeVariant.at(index.row());
+ const AircraftItemPtr item(m_items.at(index.row()));
+ return dataFromItem(item, variantIndex, role);
+ }
}
-QVariant AircraftItemModel::dataFromItem(const AircraftItem* item, quint32 variantIndex, int role) const
+QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const
{
if (role == AircraftVariantCountRole) {
return item->variants.count();
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) {
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;
}
} 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) {
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;
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"
#include <QDir>
#include <QPixmap>
#include <QStringList>
+#include <QSharedPointer>
+#include <QUrl>
#include <simgear/package/Root.hxx>
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;
class AircraftScanThread;
class QDataStream;
+struct AircraftItem;
+typedef QSharedPointer<AircraftItem> AircraftItemPtr;
+
struct AircraftItem
{
AircraftItem();
QString variantOf;
QDateTime pathModTime;
- QList<AircraftItem*> variants;
+ QList<AircraftItemPtr> variants;
private:
mutable QPixmap m_thumbnail;
};
PackageNotInstalled,
PackageInstalled,
PackageUpdateAvailable,
+ PackageQueued,
PackageDownloading
};
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);
* 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
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) {
#include <QSettings>
#include <QFileDialog>
+#include <QMessageBox>
#include "CatalogListModel.hxx"
#include "AddCatalogDialog.hxx"
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()
#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"
#include <Network/HTTPClient.hxx>
using namespace flightgear;
+using namespace simgear::pkg;
const int MAX_RECENT_AIRPORTS = 32;
const int MAX_RECENT_AIRCRAFT = 20;
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);
updateSettingsSummary();
fgInitPackageRoot();
- simgear::pkg::RootRef r(globals->packageRoot());
+ RootRef r(globals->packageRoot());
FGHTTPClient* http = new FGHTTPClient;
globals->add_subsystem("http", http);
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();
bool QtLauncher::runLauncherDialog()
{
+ sglog().setLogLevels( SG_ALL, SG_INFO );
Q_INIT_RESOURCE(resources);
// startup the nav-cache now. This pre-empts normal startup of
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();
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());
// 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);
} // 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) {
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);
}
}
}
}
-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()
}
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();
}
}
+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"
#include <QStringList>
#include <QModelIndex>
#include <QTimer>
+#include <QUrl>
+
#include <Airports/airport.hxx>
+#include <simgear/package/Package.hxx>
+#include <simgear/package/Catalog.hxx>
namespace Ui
{
void onAirportChoiceSelected(const QModelIndex& index);
void onAircraftSelected(const QModelIndex& index);
-
+ void onRequestPackageInstall(const QModelIndex& index);
+ void onCancelDownload(const QModelIndex& index);
+
void onPopupAirportHistory();
void onPopupAircraftHistory();
void onEditPaths();
void onAirportDiagramClicked(FGRunwayRef rwy);
+
+ void onAircraftInstalledCompleted(QModelIndex index);
+ void onAircraftInstallFailed(QModelIndex index, QString errorMessage);
private:
void setAirport(FGAirportRef ref);
void updateSelectedAircraft();
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];
// 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
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()
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)
// 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) {
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)
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