1 // AircraftModel.cxx - part of GUI launcher using Qt5
3 // Written by James Turner, started March 2015.
5 // Copyright (C) 2014 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>
32 #include <simgear/props/props_io.hxx>
33 #include <simgear/structure/exception.hxx>
34 #include <simgear/misc/sg_path.hxx>
37 #include <Main/globals.hxx>
39 AircraftItem::AircraftItem()
41 // oh for C++11 initialisers
42 for (int i=0; i<4; ++i) ratings[i] = 0;
45 AircraftItem::AircraftItem(QDir dir, QString filePath)
47 for (int i=0; i<4; ++i) ratings[i] = 0;
50 readProperties(filePath.toStdString(), &root);
52 if (!root.hasChild("sim")) {
53 throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
56 SGPropertyNode_ptr sim = root.getNode("sim");
59 pathModTime = QFileInfo(path).lastModified();
61 description = sim->getStringValue("description");
62 authors = sim->getStringValue("author");
64 if (sim->hasChild("rating")) {
65 SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
66 ratings[0] = ratingsNode->getIntValue("FDM");
67 ratings[1] = ratingsNode->getIntValue("systems");
68 ratings[2] = ratingsNode->getIntValue("cockpit");
69 ratings[3] = ratingsNode->getIntValue("model");
73 if (sim->hasChild("variant-of")) {
74 variantOf = sim->getStringValue("variant-of");
78 QString AircraftItem::baseName() const
80 QString fn = QFileInfo(path).fileName();
81 fn.truncate(fn.count() - 8);
85 void AircraftItem::fromDataStream(QDataStream& ds)
87 ds >> path >> description >> authors >> variantOf;
88 for (int i=0; i<4; ++i) ds >> ratings[i];
92 void AircraftItem::toDataStream(QDataStream& ds) const
94 ds << path << description << authors << variantOf;
95 for (int i=0; i<4; ++i) ds << ratings[i];
99 QPixmap AircraftItem::thumbnail() const
101 if (m_thumbnail.isNull()) {
102 QFileInfo info(path);
103 QDir dir = info.dir();
104 if (dir.exists("thumbnail.jpg")) {
105 m_thumbnail.load(dir.filePath("thumbnail.jpg"));
106 // resize to the standard size
107 if (m_thumbnail.height() > 128) {
108 m_thumbnail = m_thumbnail.scaledToHeight(128);
117 static int CACHE_VERSION = 2;
119 class AircraftScanThread : public QThread
123 AircraftScanThread(QStringList dirsToScan) :
129 ~AircraftScanThread()
133 /** thread-safe access to items already scanned */
134 QList<AircraftItem*> items()
136 QList<AircraftItem*> result;
137 QMutexLocker g(&m_lock);
138 result.swap(m_items);
155 Q_FOREACH(QString d, m_dirs) {
156 scanAircraftDir(QDir(d));
169 QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
170 if (!cacheData.isEmpty()) {
171 QDataStream ds(cacheData);
172 quint32 count, cacheVersion;
173 ds >> cacheVersion >> count;
175 if (cacheVersion != CACHE_VERSION) {
176 return; // mis-matched cache, version, drop
179 for (int i=0; i<count; ++i) {
180 AircraftItem* item = new AircraftItem;
181 item->fromDataStream(ds);
183 QFileInfo finfo(item->path);
184 if (!finfo.exists() || (finfo.lastModified() != item->pathModTime)) {
187 // corresponding -set.xml file still exists and is
189 m_cachedItems[item->path] = item;
191 } // of cached item iteration
198 QByteArray cacheData;
200 QDataStream ds(&cacheData, QIODevice::WriteOnly);
201 quint32 count = m_nextCache.count();
202 ds << CACHE_VERSION << count;
204 Q_FOREACH(AircraftItem* item, m_nextCache.values()) {
205 item->toDataStream(ds);
209 settings.setValue("aircraft-cache", cacheData);
212 void scanAircraftDir(QDir path)
218 filters << "*-set.xml";
219 Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
220 QDir childDir(child.absoluteFilePath());
221 QMap<QString, AircraftItem*> baseAircraft;
222 QList<AircraftItem*> variants;
224 Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
226 QString absolutePath = xmlChild.absoluteFilePath();
227 AircraftItem* item = NULL;
229 if (m_cachedItems.contains(absolutePath)) {
230 item = m_cachedItems.value(absolutePath);
232 item = new AircraftItem(childDir, absolutePath);
235 m_nextCache[absolutePath] = item;
237 if (item->variantOf.isNull()) {
238 baseAircraft.insert(item->baseName(), item);
240 variants.append(item);
242 } catch (sg_exception& e) {
249 } // of set.xml iteration
251 // bind variants to their principals
252 Q_FOREACH(AircraftItem* item, variants) {
253 if (!baseAircraft.contains(item->variantOf)) {
254 qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
259 baseAircraft.value(item->variantOf)->variants.append(item);
262 // lock mutex while we modify the items array
264 QMutexLocker g(&m_lock);
265 m_items.append(baseAircraft.values());
269 } // of subdir iteration
271 qDebug() << "scan of" << path << "took" << t.elapsed();
276 QList<AircraftItem*> m_items;
278 QMap<QString, AircraftItem* > m_cachedItems;
279 QMap<QString, AircraftItem* > m_nextCache;
284 AircraftItemModel::AircraftItemModel(QObject* pr, simgear::pkg::RootRef& rootRef) :
285 QAbstractListModel(pr),
286 m_packageRoot(rootRef)
289 Q_FOREACH(std::string ap, globals->get_aircraft_paths()) {
290 dirs << QString::fromStdString(ap);
293 SGPath rootAircraft(globals->get_fg_root());
294 rootAircraft.append("Aircraft");
295 dirs << QString::fromStdString(rootAircraft.str());
297 m_scanThread = new AircraftScanThread(dirs);
298 connect(m_scanThread, &AircraftScanThread::finished, this,
299 &AircraftItemModel::onScanFinished);
300 connect(m_scanThread, &AircraftScanThread::addedItems,
301 this, &AircraftItemModel::onScanResults);
302 m_scanThread->start();
305 AircraftItemModel::~AircraftItemModel()
308 m_scanThread->setDone();
309 m_scanThread->wait(1000);
314 QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
316 if (role == AircraftVariantRole) {
317 return m_activeVariant.at(index.row());
320 const AircraftItem* item(m_items.at(index.row()));
322 if (role == AircraftVariantCountRole) {
323 return item->variants.count();
326 if (role >= AircraftVariantDescriptionRole) {
327 int variantIndex = role - AircraftVariantDescriptionRole;
328 return item->variants.at(variantIndex)->description;
331 quint32 variantIndex = m_activeVariant.at(index.row());
333 if (variantIndex <= item->variants.count()) {
334 // show the selected variant
335 item = item->variants.at(variantIndex - 1);
339 if (role == Qt::DisplayRole) {
340 return item->description;
341 } else if (role == Qt::DecorationRole) {
342 return item->thumbnail();
343 } else if (role == AircraftPathRole) {
345 } else if (role == AircraftAuthorsRole) {
346 return item->authors;
347 } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
348 return item->ratings[role - AircraftRatingRole];
349 } else if (role == Qt::ToolTipRole) {
356 bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
358 if (role == AircraftVariantRole) {
359 m_activeVariant[index.row()] = value.toInt();
360 emit dataChanged(index, index);
367 QModelIndex AircraftItemModel::indexOfAircraftPath(QString path) const
369 for (int row=0; row <m_items.size(); ++row) {
370 const AircraftItem* item(m_items.at(row));
371 if (item->path == path) {
376 return QModelIndex();
379 void AircraftItemModel::onScanResults()
381 QList<AircraftItem*> newItems = m_scanThread->items();
382 if (newItems.isEmpty())
385 int firstRow = m_items.count();
386 int lastRow = firstRow + newItems.count() - 1;
387 beginInsertRows(QModelIndex(), firstRow, lastRow);
388 m_items.append(newItems);
390 // default variants in all cases
391 for (int i=0; i< newItems.count(); ++i) {
392 m_activeVariant.append(0);
397 void AircraftItemModel::onScanFinished()
403 #include "AircraftModel.moc"