1 // AircraftModel.cxx - part of GUI launcher using Qt5
3 // Written by James Turner, started March 2015.
5 // Copyright (C) 2015 James Turner <zakalawe@mac.com>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "AircraftModel.hxx"
26 #include <QMutexLocker>
27 #include <QDataStream>
30 #include <QSharedPointer>
33 #include <simgear/props/props_io.hxx>
34 #include <simgear/structure/exception.hxx>
35 #include <simgear/misc/sg_path.hxx>
36 #include <simgear/package/Package.hxx>
37 #include <simgear/package/Catalog.hxx>
38 #include <simgear/package/Install.hxx>
41 #include <Main/globals.hxx>
44 const int STANDARD_THUMBNAIL_HEIGHT = 128;
45 const int STANDARD_THUMBNAIL_WIDTH = 172;
47 using namespace simgear::pkg;
49 AircraftItem::AircraftItem() :
54 // oh for C++11 initialisers
55 for (int i=0; i<4; ++i) ratings[i] = 0;
58 AircraftItem::AircraftItem(QDir dir, QString filePath) :
63 for (int i=0; i<4; ++i) ratings[i] = 0;
66 readProperties(filePath.toStdString(), &root);
68 if (!root.hasChild("sim")) {
69 throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
72 SGPropertyNode_ptr sim = root.getNode("sim");
75 pathModTime = QFileInfo(path).lastModified();
76 if (sim->getBoolValue("exclude-from-gui", false)) {
81 description = sim->getStringValue("description");
82 authors = sim->getStringValue("author");
84 if (sim->hasChild("rating")) {
85 SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
86 ratings[0] = ratingsNode->getIntValue("FDM");
87 ratings[1] = ratingsNode->getIntValue("systems");
88 ratings[2] = ratingsNode->getIntValue("cockpit");
89 ratings[3] = ratingsNode->getIntValue("model");
93 if (sim->hasChild("long-description")) {
94 // clean up any XML whitspace in the text.
95 longDescription = QString(sim->getStringValue("long-description")).simplified();
98 if (sim->hasChild("variant-of")) {
99 variantOf = sim->getStringValue("variant-of");
102 if (sim->hasChild("tags")) {
103 SGPropertyNode_ptr tagsNode = sim->getChild("tags");
104 int nChildren = tagsNode->nChildren();
105 for (int i = 0; i < nChildren; i++) {
106 const SGPropertyNode* c = tagsNode->getChild(i);
107 if (strcmp(c->getName(), "tag") == 0) {
108 const char* tagName = c->getStringValue();
109 usesHeliports |= (strcmp(tagName, "helicopter") == 0);
110 // could also consider vtol tag?
111 usesSeaports |= (strcmp(tagName, "seaplane") == 0);
112 usesSeaports |= (strcmp(tagName, "floats") == 0);
114 } // of tags iteration
115 } // of set-xml has tags
118 QString AircraftItem::baseName() const
120 QString fn = QFileInfo(path).fileName();
121 fn.truncate(fn.count() - 8);
125 void AircraftItem::fromDataStream(QDataStream& ds)
127 ds >> path >> pathModTime >> excluded;
132 ds >> description >> longDescription >> authors >> variantOf;
133 for (int i=0; i<4; ++i) ds >> ratings[i];
136 void AircraftItem::toDataStream(QDataStream& ds) const
138 ds << path << pathModTime << excluded;
143 ds << description << longDescription << authors << variantOf;
144 for (int i=0; i<4; ++i) ds << ratings[i];
147 QPixmap AircraftItem::thumbnail() const
149 if (m_thumbnail.isNull()) {
150 QFileInfo info(path);
151 QDir dir = info.dir();
152 if (dir.exists("thumbnail.jpg")) {
153 m_thumbnail.load(dir.filePath("thumbnail.jpg"));
154 // resize to the standard size
155 if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) {
156 m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT);
165 static quint32 CACHE_VERSION = 5;
167 class AircraftScanThread : public QThread
171 AircraftScanThread(QStringList dirsToScan) :
177 ~AircraftScanThread()
181 /** thread-safe access to items already scanned */
182 QVector<AircraftItemPtr> items()
184 QVector<AircraftItemPtr> result;
185 QMutexLocker g(&m_lock);
186 result.swap(m_items);
203 Q_FOREACH(QString d, m_dirs) {
204 scanAircraftDir(QDir(d));
217 QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
218 if (!cacheData.isEmpty()) {
219 QDataStream ds(cacheData);
220 quint32 count, cacheVersion;
221 ds >> cacheVersion >> count;
223 if (cacheVersion != CACHE_VERSION) {
224 return; // mis-matched cache, version, drop
227 for (quint32 i=0; i<count; ++i) {
228 AircraftItemPtr item(new AircraftItem);
229 item->fromDataStream(ds);
231 QFileInfo finfo(item->path);
232 if (finfo.exists() && (finfo.lastModified() == item->pathModTime)) {
233 // corresponding -set.xml file still exists and is
235 m_cachedItems[item->path] = item;
237 } // of cached item iteration
244 QByteArray cacheData;
246 QDataStream ds(&cacheData, QIODevice::WriteOnly);
247 quint32 count = m_nextCache.count();
248 ds << CACHE_VERSION << count;
250 Q_FOREACH(AircraftItemPtr item, m_nextCache.values()) {
251 item->toDataStream(ds);
255 settings.setValue("aircraft-cache", cacheData);
258 void scanAircraftDir(QDir path)
264 filters << "*-set.xml";
265 Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
266 QDir childDir(child.absoluteFilePath());
267 QMap<QString, AircraftItemPtr> baseAircraft;
268 QList<AircraftItemPtr> variants;
270 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
272 QString absolutePath = xmlChild.absoluteFilePath();
273 AircraftItemPtr item;
275 if (m_cachedItems.contains(absolutePath)) {
276 item = m_cachedItems.value(absolutePath);
278 item = AircraftItemPtr(new AircraftItem(childDir, absolutePath));
281 m_nextCache[absolutePath] = item;
283 if (item->excluded) {
287 if (item->variantOf.isNull()) {
288 baseAircraft.insert(item->baseName(), item);
290 variants.append(item);
292 } catch (sg_exception& e) {
299 } // of set.xml iteration
301 // bind variants to their principals
302 Q_FOREACH(AircraftItemPtr item, variants) {
303 if (!baseAircraft.contains(item->variantOf)) {
304 qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
308 baseAircraft.value(item->variantOf)->variants.append(item);
311 // lock mutex while we modify the items array
313 QMutexLocker g(&m_lock);
314 m_items+=(baseAircraft.values().toVector());
318 } // of subdir iteration
323 QVector<AircraftItemPtr> m_items;
325 QMap<QString, AircraftItemPtr > m_cachedItems;
326 QMap<QString, AircraftItemPtr > m_nextCache;
331 class PackageDelegate : public simgear::pkg::Delegate
334 PackageDelegate(AircraftItemModel* model) :
337 m_model->m_packageRoot->addDelegate(this);
342 m_model->m_packageRoot->removeDelegate(this);
346 virtual void catalogRefreshed(CatalogRef aCatalog, StatusCode aReason)
348 if (aReason == STATUS_IN_PROGRESS) {
349 qDebug() << "doing refresh of" << QString::fromStdString(aCatalog->url());
350 } else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) {
351 m_model->refreshPackages();
353 qWarning() << "failed refresh of "
354 << QString::fromStdString(aCatalog->url()) << ":" << aReason << endl;
358 virtual void startInstall(InstallRef aInstall)
360 QModelIndex mi(indexForPackage(aInstall->package()));
361 m_model->dataChanged(mi, mi);
364 virtual void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total)
368 QModelIndex mi(indexForPackage(aInstall->package()));
369 m_model->dataChanged(mi, mi);
372 virtual void finishInstall(InstallRef aInstall, StatusCode aReason)
374 QModelIndex mi(indexForPackage(aInstall->package()));
375 m_model->dataChanged(mi, mi);
377 if ((aReason != USER_CANCELLED) && (aReason != STATUS_SUCCESS)) {
378 m_model->installFailed(mi, aReason);
381 if (aReason == STATUS_SUCCESS) {
382 m_model->installSucceeded(mi);
386 virtual void availablePackagesChanged() Q_DECL_OVERRIDE
388 m_model->refreshPackages();
391 virtual void dataForThumbnail(const std::string& aThumbnailUrl,
392 size_t length, const uint8_t* bytes)
394 QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes), length));
396 qWarning() << "failed to load image data for URL:" <<
397 QString::fromStdString(aThumbnailUrl);
401 QPixmap pix = QPixmap::fromImage(img);
402 if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) {
403 pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT);
405 m_model->m_thumbnailPixmapCache.insert(QString::fromStdString(aThumbnailUrl),
408 // notify any affected items. Linear scan here avoids another map/dict
410 PackageList::const_iterator it;
413 for (it=m_model->m_packages.begin(); it != m_model->m_packages.end(); ++it, ++i) {
414 const string_list& urls((*it)->thumbnailUrls());
415 string_list::const_iterator cit = std::find(urls.begin(), urls.end(), aThumbnailUrl);
416 if (cit != urls.end()) {
417 QModelIndex mi(m_model->index(i + m_model->m_items.size()));
418 m_model->dataChanged(mi, mi);
420 } // of packages iteration
424 QModelIndex indexForPackage(const PackageRef& ref) const
426 PackageList::const_iterator it = std::find(m_model->m_packages.begin(),
427 m_model->m_packages.end(),
429 if (it == m_model->m_packages.end()) {
430 return QModelIndex();
433 size_t offset = it - m_model->m_packages.begin();
434 return m_model->index(offset + m_model->m_items.size());
437 AircraftItemModel* m_model;
440 AircraftItemModel::AircraftItemModel(QObject* pr ) :
441 QAbstractListModel(pr),
443 m_showOfficialHangarMessage(false)
447 AircraftItemModel::~AircraftItemModel()
449 abandonCurrentScan();
453 void AircraftItemModel::setPackageRoot(const simgear::pkg::RootRef& root)
460 m_packageRoot = root;
463 m_delegate = new PackageDelegate(this);
464 // packages may already be refreshed, so pull now
469 void AircraftItemModel::setPaths(QStringList paths)
474 void AircraftItemModel::setOfficialHangarMessageVisible(bool vis)
476 if (m_showOfficialHangarMessage == vis) {
480 m_showOfficialHangarMessage = vis;
483 beginInsertRows(QModelIndex(), 0, 0);
484 m_items.prepend(AircraftItemPtr(new AircraftItem));
485 m_activeVariant.prepend(0);
488 beginRemoveRows(QModelIndex(), 0, 0);
490 m_activeVariant.removeAt(0);
495 QModelIndex AircraftItemModel::officialHangarMessageIndex() const
497 if (!m_showOfficialHangarMessage) {
498 return QModelIndex();
504 void AircraftItemModel::scanDirs()
506 abandonCurrentScan();
508 int firstRow = (m_showOfficialHangarMessage ? 1 : 0);
509 int numToRemove = m_items.size() - firstRow;
510 int lastRow = firstRow + numToRemove - 1;
512 beginRemoveRows(QModelIndex(), firstRow, lastRow);
513 m_items.remove(firstRow, numToRemove);
514 m_activeVariant.remove(firstRow, numToRemove);
517 QStringList dirs = m_paths;
519 Q_FOREACH(std::string ap, globals->get_aircraft_paths()) {
520 dirs << QString::fromStdString(ap);
523 SGPath rootAircraft(globals->get_fg_root());
524 rootAircraft.append("Aircraft");
525 dirs << QString::fromStdString(rootAircraft.str());
527 m_scanThread = new AircraftScanThread(dirs);
528 connect(m_scanThread, &AircraftScanThread::finished, this,
529 &AircraftItemModel::onScanFinished);
530 connect(m_scanThread, &AircraftScanThread::addedItems,
531 this, &AircraftItemModel::onScanResults);
532 m_scanThread->start();
535 void AircraftItemModel::abandonCurrentScan()
538 m_scanThread->setDone();
539 m_scanThread->wait(1000);
545 void AircraftItemModel::refreshPackages()
547 simgear::pkg::PackageList newPkgs = m_packageRoot->allPackages();
548 int firstRow = m_items.size();
549 int newSize = newPkgs.size();
551 if (m_packages.size() != newPkgs.size()) {
552 int oldSize = m_packages.size();
553 if (newSize > oldSize) {
555 int firstNewRow = firstRow + oldSize;
556 int lastNewRow = firstRow + newSize - 1;
557 beginInsertRows(QModelIndex(), firstNewRow, lastNewRow);
558 m_packages = newPkgs;
559 m_packageVariant.resize(newSize);
563 int firstOldRow = firstRow + newSize;
564 int lastOldRow = firstRow + oldSize - 1;
565 beginRemoveRows(QModelIndex(), firstOldRow, lastOldRow);
566 m_packages = newPkgs;
567 m_packageVariant.resize(newSize);
571 m_packages = newPkgs;
574 emit dataChanged(index(firstRow), index(firstRow + newSize - 1));
577 int AircraftItemModel::rowCount(const QModelIndex& parent) const
579 return m_items.size() + m_packages.size();
582 QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
584 int row = index.row();
585 if (m_showOfficialHangarMessage) {
587 if (role == AircraftPackageStatusRole) {
588 return NoOfficialCatalogMessage;
595 if (row >= m_items.size()) {
596 quint32 packageIndex = row - m_items.size();
598 if (role == AircraftVariantRole) {
599 return m_packageVariant.at(packageIndex);
602 const PackageRef& pkg(m_packages[packageIndex]);
603 InstallRef ex = pkg->existingInstall();
605 if (role == AircraftInstallPercentRole) {
606 return ex.valid() ? ex->downloadedPercent() : 0;
607 } else if (role == AircraftInstallDownloadedSizeRole) {
608 return static_cast<quint64>(ex.valid() ? ex->downloadedBytes() : 0);
611 quint32 variantIndex = m_packageVariant.at(packageIndex);
612 return dataFromPackage(pkg, variantIndex, role);
614 if (role == AircraftVariantRole) {
615 return m_activeVariant.at(row);
618 quint32 variantIndex = m_activeVariant.at(row);
619 const AircraftItemPtr item(m_items.at(row));
620 return dataFromItem(item, variantIndex, role);
624 QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, quint32 variantIndex, int role) const
626 if (role == AircraftVariantCountRole) {
627 return item->variants.count();
630 if (role == AircraftThumbnailCountRole) {
631 QPixmap p = item->thumbnail();
632 return p.isNull() ? 0 : 1;
635 if (role == AircraftThumbnailSizeRole) {
636 return item->thumbnail().size();
639 if ((role >= AircraftVariantDescriptionRole) && (role < AircraftThumbnailRole)) {
640 int variantIndex = role - AircraftVariantDescriptionRole;
641 return item->variants.at(variantIndex)->description;
645 if (variantIndex <= static_cast<quint32>(item->variants.count())) {
646 // show the selected variant
647 item = item->variants.at(variantIndex - 1);
651 if (role == Qt::DisplayRole) {
652 if (item->description.isEmpty()) {
653 return tr("Missing description for: %1").arg(item->baseName());
656 return item->description;
657 } else if (role == Qt::DecorationRole) {
658 return item->thumbnail();
659 } else if (role == AircraftPathRole) {
661 } else if (role == AircraftAuthorsRole) {
662 return item->authors;
663 } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
664 return item->ratings[role - AircraftRatingRole];
665 } else if (role >= AircraftThumbnailRole) {
666 return item->thumbnail();
667 } else if (role == AircraftPackageIdRole) {
668 // can we fake an ID? otherwise fall through to a null variant
669 } else if (role == AircraftPackageStatusRole) {
670 return PackageInstalled; // always the case
671 } else if (role == Qt::ToolTipRole) {
673 } else if (role == AircraftURIRole) {
674 return QUrl::fromLocalFile(item->path);
675 } else if (role == AircraftHasRatingsRole) {
677 for (int i=0; i<4; ++i) {
678 have |= (item->ratings[i] > 0);
681 } else if (role == AircraftLongDescriptionRole) {
682 return item->longDescription;
683 } else if (role == AircraftIsHelicopterRole) {
684 return item->usesHeliports;
685 } else if (role == AircraftIsSeaplaneRole) {
686 return item->usesSeaports;
692 QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, quint32 variantIndex, int role) const
694 if (role == Qt::DecorationRole) {
695 role = AircraftThumbnailRole; // use first thumbnail
698 if ((role >= AircraftVariantDescriptionRole) && (role < AircraftThumbnailRole)) {
699 int variantIndex = role - AircraftVariantDescriptionRole;
700 QString desc = QString::fromStdString(item->nameForVariant(variantIndex));
701 if (desc.isEmpty()) {
702 desc = tr("Missing description for: %1").arg(QString::fromStdString(item->id()));
707 if (role == Qt::DisplayRole) {
708 QString desc = QString::fromStdString(item->nameForVariant(variantIndex));
709 if (desc.isEmpty()) {
710 desc = tr("Missing description for: %1").arg(QString::fromStdString(item->id()));
713 } else if (role == AircraftPathRole) {
714 InstallRef i = item->existingInstall();
716 return QString::fromStdString(i->primarySetPath().str());
718 } else if (role == AircraftPackageIdRole) {
719 return QString::fromStdString(item->variants()[variantIndex]);
720 } else if (role == AircraftPackageStatusRole) {
721 InstallRef i = item->existingInstall();
723 if (i->isDownloading()) {
724 return PackageDownloading;
727 return PackageQueued;
729 if (i->hasUpdate()) {
730 return PackageUpdateAvailable;
733 return PackageInstalled;
735 return PackageNotInstalled;
737 } else if (role == AircraftVariantCountRole) {
738 // this value wants the number of aditional variants, i.e not
739 // including the primary. Hence the -1 term.
740 return static_cast<quint32>(item->variants().size() - 1);
741 } else if (role == AircraftThumbnailSizeRole) {
742 QPixmap pm = packageThumbnail(item, 0, false).value<QPixmap>();
744 return QSize(STANDARD_THUMBNAIL_WIDTH, STANDARD_THUMBNAIL_HEIGHT);
746 } else if (role >= AircraftThumbnailRole) {
747 return packageThumbnail(item , role - AircraftThumbnailRole);
748 } else if (role == AircraftAuthorsRole) {
749 SGPropertyNode* authors = item->properties()->getChild("author");
751 return QString::fromStdString(authors->getStringValue());
753 } else if (role == AircraftLongDescriptionRole) {
754 return QString::fromStdString(item->description()).simplified();
755 } else if (role == AircraftPackageSizeRole) {
756 return static_cast<int>(item->fileSizeBytes());
757 } else if (role == AircraftURIRole) {
758 return QUrl("package:" + QString::fromStdString(item->qualifiedVariantId(variantIndex)));
759 } else if (role == AircraftHasRatingsRole) {
760 return item->properties()->hasChild("rating");
761 } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
762 int ratingIndex = role - AircraftRatingRole;
763 SGPropertyNode* ratings = item->properties()->getChild("rating");
767 return ratings->getChild(ratingIndex)->getIntValue();
773 QVariant AircraftItemModel::packageThumbnail(PackageRef p, int index, bool download) const
775 const string_list& thumbnails(p->thumbnailUrls());
776 if (index >= static_cast<int>(thumbnails.size())) {
780 std::string thumbnailUrl = thumbnails.at(index);
781 QString urlQString(QString::fromStdString(thumbnailUrl));
782 if (m_thumbnailPixmapCache.contains(urlQString)) {
784 return m_thumbnailPixmapCache.value(urlQString);
787 // check the on-disk store. This relies on the order of thumbnails in the
788 // results of thumbnailUrls and thumbnails corresponding
789 InstallRef ex = p->existingInstall();
791 const string_list& thumbNames(p->thumbnails());
792 if (!thumbNames.empty()) {
793 SGPath path(ex->path());
794 path.append(p->thumbnails()[index]);
797 pix.load(QString::fromStdString(path.str()));
798 // resize to the standard size
799 if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) {
800 pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT);
802 m_thumbnailPixmapCache[urlQString] = pix;
805 } // of have thumbnail file names
806 } // of have existing install
809 m_packageRoot->requestThumbnailData(thumbnailUrl);
814 bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
816 int row = index.row();
817 if (role == AircraftVariantRole) {
818 if (row >= m_activeVariant.size()) {
819 row -= m_activeVariant.size();
820 m_packageVariant[row] = value.toInt();
822 m_activeVariant[row] = value.toInt();
825 emit dataChanged(index, index);
832 QModelIndex AircraftItemModel::indexOfAircraftURI(QUrl uri) const
835 return QModelIndex();
838 if (uri.isLocalFile()) {
839 QString path = uri.toLocalFile();
840 for (int row=0; row <m_items.size(); ++row) {
841 const AircraftItemPtr item(m_items.at(row));
842 if (item->path == path) {
846 // check variants too
847 for (int vr=0; vr < item->variants.size(); ++vr) {
848 if (item->variants.at(vr)->path == path) {
853 } else if (uri.scheme() == "package") {
854 QString ident = uri.path();
855 int rowOffset = m_items.size();
857 PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
859 for (size_t i=0; i < m_packages.size(); ++i) {
860 if (m_packages[i] == pkg) {
861 return index(rowOffset + i);
863 } // of linear package scan
865 } else if (uri.scheme() == "") {
866 // Empty URI scheme (no selection), nothing to do
868 qWarning() << "Unknown aircraft URI scheme" << uri << uri.scheme();
871 return QModelIndex();
874 void AircraftItemModel::onScanResults()
876 QVector<AircraftItemPtr> newItems = m_scanThread->items();
877 if (newItems.isEmpty())
880 int firstRow = m_items.count();
881 int lastRow = firstRow + newItems.count() - 1;
882 beginInsertRows(QModelIndex(), firstRow, lastRow);
885 // default variants in all cases
886 for (int i=0; i< newItems.count(); ++i) {
887 m_activeVariant.append(0);
892 void AircraftItemModel::onScanFinished()
896 emit scanCompleted();
899 void AircraftItemModel::installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason)
903 case Delegate::FAIL_CHECKSUM:
904 msg = tr("Invalid package checksum"); break;
905 case Delegate::FAIL_DOWNLOAD:
906 msg = tr("Download failed"); break;
907 case Delegate::FAIL_EXTRACT:
908 msg = tr("Package could not be extracted"); break;
909 case Delegate::FAIL_FILESYSTEM:
910 msg = tr("A local file-system error occurred"); break;
911 case Delegate::FAIL_NOT_FOUND:
912 msg = tr("Package file missing from download server"); break;
913 case Delegate::FAIL_UNKNOWN:
915 msg = tr("Unknown reason");
918 emit aircraftInstallFailed(index, msg);
921 void AircraftItemModel::installSucceeded(QModelIndex index)
923 emit aircraftInstallCompleted(index);
926 bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
928 if (index.row() < m_items.size()) {
929 return true; // local file, always runnable
932 quint32 packageIndex = index.row() - m_items.size();
933 const PackageRef& pkg(m_packages[packageIndex]);
934 InstallRef ex = pkg->existingInstall();
936 return false; // not installed
939 return !ex->isDownloading();
942 bool AircraftItemModel::isCandidateAircraftPath(QString path)
945 filters << "*-set.xml";
950 Q_FOREACH(QFileInfo child, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
951 QDir childDir(child.absoluteFilePath());
953 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
957 if ((setXmlCount > 0) || (dirCount > 10)) {
962 return (setXmlCount > 0);
966 #include "AircraftModel.moc"