]> git.mxchange.org Git - flightgear.git/blob - src/GUI/AircraftModel.cxx
Refactor aircraft helper classes
[flightgear.git] / src / GUI / AircraftModel.cxx
1 // AircraftModel.cxx - part of GUI launcher using Qt5
2 //
3 // Written by James Turner, started March 2015.
4 //
5 // Copyright (C) 2014 James Turner <zakalawe@mac.com>
6 //
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.
11 //
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.
16 //
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.
20
21 #include "AircraftModel.hxx"
22
23 #include <QDir>
24 #include <QThread>
25 #include <QMutex>
26 #include <QMutexLocker>
27 #include <QDataStream>
28 #include <QSettings>
29 #include <QDebug>
30
31 // Simgear
32 #include <simgear/props/props_io.hxx>
33 #include <simgear/structure/exception.hxx>
34 #include <simgear/misc/sg_path.hxx>
35
36 // FlightGear
37 #include <Main/globals.hxx>
38
39 AircraftItem::AircraftItem()
40 {
41     // oh for C++11 initialisers
42     for (int i=0; i<4; ++i) ratings[i] = 0;
43 }
44
45 AircraftItem::AircraftItem(QDir dir, QString filePath)
46 {
47     for (int i=0; i<4; ++i) ratings[i] = 0;
48
49     SGPropertyNode root;
50     readProperties(filePath.toStdString(), &root);
51
52     if (!root.hasChild("sim")) {
53         throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
54     }
55
56     SGPropertyNode_ptr sim = root.getNode("sim");
57
58     path = filePath;
59     pathModTime = QFileInfo(path).lastModified();
60
61     description = sim->getStringValue("description");
62     authors =  sim->getStringValue("author");
63
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");
70
71     }
72
73     if (sim->hasChild("variant-of")) {
74         variantOf = sim->getStringValue("variant-of");
75     }
76 }
77
78 QString AircraftItem::baseName() const
79 {
80     QString fn = QFileInfo(path).fileName();
81     fn.truncate(fn.count() - 8);
82     return fn;
83 }
84
85 void AircraftItem::fromDataStream(QDataStream& ds)
86 {
87     ds >> path >> description >> authors >> variantOf;
88     for (int i=0; i<4; ++i) ds >> ratings[i];
89     ds >> pathModTime;
90 }
91
92 void AircraftItem::toDataStream(QDataStream& ds) const
93 {
94     ds << path << description << authors << variantOf;
95     for (int i=0; i<4; ++i) ds << ratings[i];
96     ds << pathModTime;
97 }
98
99 QPixmap AircraftItem::thumbnail() const
100 {
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);
109             }
110         }
111     }
112
113     return m_thumbnail;
114 }
115
116
117 static int CACHE_VERSION = 2;
118
119 class AircraftScanThread : public QThread
120 {
121     Q_OBJECT
122 public:
123     AircraftScanThread(QStringList dirsToScan) :
124         m_dirs(dirsToScan),
125         m_done(false)
126     {
127     }
128
129     ~AircraftScanThread()
130     {
131     }
132
133     /** thread-safe access to items already scanned */
134     QList<AircraftItem*> items()
135     {
136         QList<AircraftItem*> result;
137         QMutexLocker g(&m_lock);
138         result.swap(m_items);
139         g.unlock();
140         return result;
141     }
142
143     void setDone()
144     {
145         m_done = true;
146     }
147 Q_SIGNALS:
148     void addedItems();
149
150 protected:
151     virtual void run()
152     {
153         readCache();
154
155         Q_FOREACH(QString d, m_dirs) {
156             scanAircraftDir(QDir(d));
157             if (m_done) {
158                 return;
159             }
160         }
161
162         writeCache();
163     }
164
165 private:
166     void readCache()
167     {
168         QSettings settings;
169         QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
170         if (!cacheData.isEmpty()) {
171             QDataStream ds(cacheData);
172             quint32 count, cacheVersion;
173             ds >> cacheVersion >> count;
174
175             if (cacheVersion != CACHE_VERSION) {
176                 return; // mis-matched cache, version, drop
177             }
178
179              for (int i=0; i<count; ++i) {
180                 AircraftItem* item = new AircraftItem;
181                 item->fromDataStream(ds);
182
183                 QFileInfo finfo(item->path);
184                 if (!finfo.exists() || (finfo.lastModified() != item->pathModTime)) {
185                     delete item;
186                 } else {
187                     // corresponding -set.xml file still exists and is
188                     // unmodified
189                     m_cachedItems[item->path] = item;
190                 }
191             } // of cached item iteration
192         }
193     }
194
195     void writeCache()
196     {
197         QSettings settings;
198         QByteArray cacheData;
199         {
200             QDataStream ds(&cacheData, QIODevice::WriteOnly);
201             quint32 count = m_nextCache.count();
202             ds << CACHE_VERSION << count;
203
204             Q_FOREACH(AircraftItem* item, m_nextCache.values()) {
205                 item->toDataStream(ds);
206             }
207         }
208
209         settings.setValue("aircraft-cache", cacheData);
210     }
211
212     void scanAircraftDir(QDir path)
213     {
214         QTime t;
215         t.start();
216
217         QStringList filters;
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;
223
224             Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
225                 try {
226                     QString absolutePath = xmlChild.absoluteFilePath();
227                     AircraftItem* item = NULL;
228
229                     if (m_cachedItems.contains(absolutePath)) {
230                         item = m_cachedItems.value(absolutePath);
231                     } else {
232                         item = new AircraftItem(childDir, absolutePath);
233                     }
234
235                     m_nextCache[absolutePath] = item;
236
237                     if (item->variantOf.isNull()) {
238                         baseAircraft.insert(item->baseName(), item);
239                     } else {
240                         variants.append(item);
241                     }
242                 } catch (sg_exception& e) {
243                     continue;
244                 }
245
246                 if (m_done) {
247                     return;
248                 }
249             } // of set.xml iteration
250
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;
255                     delete item;
256                     continue;
257                 }
258
259                 baseAircraft.value(item->variantOf)->variants.append(item);
260             }
261
262             // lock mutex while we modify the items array
263             {
264                 QMutexLocker g(&m_lock);
265                 m_items.append(baseAircraft.values());
266             }
267
268             emit addedItems();
269         } // of subdir iteration
270
271         qDebug() << "scan of" << path << "took" << t.elapsed();
272     }
273
274     QMutex m_lock;
275     QStringList m_dirs;
276     QList<AircraftItem*> m_items;
277
278     QMap<QString, AircraftItem* > m_cachedItems;
279     QMap<QString, AircraftItem* > m_nextCache;
280
281     bool m_done;
282 };
283
284 AircraftItemModel::AircraftItemModel(QObject* pr) :
285     QAbstractListModel(pr)
286 {
287     QStringList dirs;
288     Q_FOREACH(std::string ap, globals->get_aircraft_paths()) {
289         dirs << QString::fromStdString(ap);
290     }
291
292     SGPath rootAircraft(globals->get_fg_root());
293     rootAircraft.append("Aircraft");
294     dirs << QString::fromStdString(rootAircraft.str());
295
296     m_scanThread = new AircraftScanThread(dirs);
297     connect(m_scanThread, &AircraftScanThread::finished, this,
298             &AircraftItemModel::onScanFinished);
299     connect(m_scanThread, &AircraftScanThread::addedItems,
300             this, &AircraftItemModel::onScanResults);
301     m_scanThread->start();
302 }
303
304 AircraftItemModel::~AircraftItemModel()
305 {
306     if (m_scanThread) {
307         m_scanThread->setDone();
308         m_scanThread->wait(1000);
309         delete m_scanThread;
310     }
311 }
312
313 QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
314   {
315       if (role == AircraftVariantRole) {
316           return m_activeVariant.at(index.row());
317       }
318
319       const AircraftItem* item(m_items.at(index.row()));
320
321       if (role == AircraftVariantCountRole) {
322           return item->variants.count();
323       }
324
325       if (role >= AircraftVariantDescriptionRole) {
326           int variantIndex = role - AircraftVariantDescriptionRole;
327           return item->variants.at(variantIndex)->description;
328       }
329
330       quint32 variantIndex = m_activeVariant.at(index.row());
331       if (variantIndex) {
332           if (variantIndex <= item->variants.count()) {
333               // show the selected variant
334               item = item->variants.at(variantIndex - 1);
335           }
336       }
337
338       if (role == Qt::DisplayRole) {
339           return item->description;
340       } else if (role == Qt::DecorationRole) {
341           return item->thumbnail();
342       } else if (role == AircraftPathRole) {
343           return item->path;
344       } else if (role == AircraftAuthorsRole) {
345           return item->authors;
346       } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
347           return item->ratings[role - AircraftRatingRole];
348       } else if (role == Qt::ToolTipRole) {
349           return item->path;
350       }
351
352       return QVariant();
353   }
354
355 bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
356   {
357       if (role == AircraftVariantRole) {
358           m_activeVariant[index.row()] = value.toInt();
359           emit dataChanged(index, index);
360           return true;
361       }
362
363       return false;
364   }
365
366 QModelIndex AircraftItemModel::indexOfAircraftPath(QString path) const
367 {
368     for (int row=0; row <m_items.size(); ++row) {
369         const AircraftItem* item(m_items.at(row));
370         if (item->path == path) {
371             return index(row);
372         }
373     }
374
375     return QModelIndex();
376 }
377
378 void AircraftItemModel::onScanResults()
379 {
380     QList<AircraftItem*> newItems = m_scanThread->items();
381     if (newItems.isEmpty())
382         return;
383
384     int firstRow = m_items.count();
385     int lastRow = firstRow + newItems.count() - 1;
386     beginInsertRows(QModelIndex(), firstRow, lastRow);
387     m_items.append(newItems);
388
389     // default variants in all cases
390     for (int i=0; i< newItems.count(); ++i) {
391         m_activeVariant.append(0);
392     }
393     endInsertRows();
394 }
395
396 void AircraftItemModel::onScanFinished()
397 {
398     delete m_scanThread;
399     m_scanThread = NULL;
400 }
401
402 #include "AircraftModel.moc"