--- /dev/null
+// AircraftItemDelegate.cxx - part of GUI launcher using Qt5
+//
+// Written by James Turner, started March 2015.
+//
+// Copyright (C) 2014 James Turner <zakalawe@mac.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+
+#include "AircraftItemDelegate.hxx"
+
+#include <QDebug>
+#include <QPainter>
+#include <QLinearGradient>
+#include <QListView>
+#include <QMouseEvent>
+
+#include "AircraftModel.hxx"
+
+AircraftItemDelegate::AircraftItemDelegate(QListView* view) :
+ m_view(view)
+{
+ view->viewport()->installEventFilter(this);
+
+ m_leftArrowIcon.load(":/left-arrow-icon");
+ m_rightArrowIcon.load(":/right-arrow-icon");
+}
+
+void AircraftItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option,
+ const QModelIndex & index) const
+{
+ // selection feedback rendering
+ if (option.state & QStyle::State_Selected) {
+ QLinearGradient grad(option.rect.topLeft(), option.rect.bottomLeft());
+ grad.setColorAt(0.0, QColor(152, 163, 180));
+ grad.setColorAt(1.0, QColor(90, 107, 131));
+
+ QBrush backgroundBrush(grad);
+ painter->fillRect(option.rect, backgroundBrush);
+
+ painter->setPen(QColor(90, 107, 131));
+ painter->drawLine(option.rect.topLeft(), option.rect.topRight());
+
+ }
+
+ QRect contentRect = option.rect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
+
+ QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
+ painter->drawPixmap(contentRect.topLeft(), thumbnail);
+
+ // draw 1px frame
+ painter->setPen(QColor(0x7f, 0x7f, 0x7f));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawRect(contentRect.left(), contentRect.top(), thumbnail.width(), thumbnail.height());
+
+ int variantCount = index.data(AircraftVariantCountRole).toInt();
+ int currentVariant =index.data(AircraftVariantRole).toInt();
+ QString description = index.data(Qt::DisplayRole).toString();
+ contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
+
+ painter->setPen(Qt::black);
+ QFont f;
+ f.setPointSize(18);
+ painter->setFont(f);
+
+ QRect descriptionRect = contentRect.adjusted(ARROW_SIZE, 0, -ARROW_SIZE, 0),
+ actualBounds;
+
+ if (variantCount > 0) {
+ bool canLeft = (currentVariant > 0);
+ bool canRight = (currentVariant < variantCount );
+
+ QRect leftArrowRect = leftCycleArrowRect(option.rect, index);
+ if (canLeft) {
+ painter->drawPixmap(leftArrowRect.topLeft() + QPoint(2, 2), m_leftArrowIcon);
+ }
+
+ QRect rightArrowRect = rightCycleArrowRect(option.rect, index);
+ if (canRight) {
+ painter->drawPixmap(rightArrowRect.topLeft() + QPoint(2, 2), m_rightArrowIcon);
+ }
+ }
+
+ painter->drawText(descriptionRect, Qt::TextWordWrap, description, &actualBounds);
+
+ QString authors = index.data(AircraftAuthorsRole).toString();
+
+ f.setPointSize(12);
+ painter->setFont(f);
+
+ QRect authorsRect = descriptionRect;
+ authorsRect.moveTop(actualBounds.bottom() + MARGIN);
+ painter->drawText(authorsRect, Qt::TextWordWrap,
+ QString("by: %1").arg(authors),
+ &actualBounds);
+
+ QRect r = contentRect;
+ r.setWidth(contentRect.width() / 2);
+ r.moveTop(actualBounds.bottom() + MARGIN);
+ r.setHeight(24);
+
+ drawRating(painter, "Flight model:", r, index.data(AircraftRatingRole).toInt());
+ r.moveTop(r.bottom());
+ drawRating(painter, "Systems:", r, index.data(AircraftRatingRole + 1).toInt());
+
+ r.moveTop(actualBounds.bottom() + MARGIN);
+ r.moveLeft(r.right());
+ drawRating(painter, "Cockpit:", r, index.data(AircraftRatingRole + 2).toInt());
+ r.moveTop(r.bottom());
+ drawRating(painter, "Exterior model:", r, index.data(AircraftRatingRole + 3).toInt());
+}
+
+QSize AircraftItemDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
+{
+ return QSize(500, 128 + (MARGIN * 2));
+}
+
+bool AircraftItemDelegate::eventFilter( QObject*, QEvent* event )
+{
+ if ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
+ {
+ QMouseEvent* me = static_cast< QMouseEvent* >( event );
+ QModelIndex index = m_view->indexAt( me->pos() );
+ int variantCount = index.data(AircraftVariantCountRole).toInt();
+ int variantIndex = index.data(AircraftVariantRole).toInt();
+
+ if ( (event->type() == QEvent::MouseButtonRelease) && (variantCount > 0) )
+ {
+ QRect vr = m_view->visualRect(index);
+ QRect leftCycleRect = leftCycleArrowRect(vr, index),
+ rightCycleRect = rightCycleArrowRect(vr, index);
+
+ if ((variantIndex > 0) && leftCycleRect.contains(me->pos())) {
+ m_view->model()->setData(index, variantIndex - 1, AircraftVariantRole);
+ emit variantChanged(index);
+ return true;
+ } else if ((variantIndex < variantCount) && rightCycleRect.contains(me->pos())) {
+ m_view->model()->setData(index, variantIndex + 1, AircraftVariantRole);
+ emit variantChanged(index);
+ return true;
+ }
+ }
+ } // of mouse button press or release
+
+ return false;
+}
+
+
+QRect AircraftItemDelegate::leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
+{
+ QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
+ QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
+ contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
+
+ QRect r = contentRect;
+ r.setRight(r.left() + ARROW_SIZE);
+ r.setBottom(r.top() + ARROW_SIZE);
+ return r;
+
+}
+
+QRect AircraftItemDelegate::rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
+{
+ QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
+ QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
+ contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
+
+ QRect r = contentRect;
+ r.setLeft(r.right() - ARROW_SIZE);
+ r.setBottom(r.top() + ARROW_SIZE);
+ return r;
+
+}
+
+void AircraftItemDelegate::drawRating(QPainter* painter, QString label, const QRect& box, int value) const
+{
+ const int DOT_SIZE = 10;
+ const int DOT_MARGIN = 4;
+
+ QRect dotBox = box;
+ dotBox.setLeft(box.right() - (DOT_MARGIN * 6 + DOT_SIZE * 5));
+
+ painter->setPen(Qt::black);
+ QRect textBox = box;
+ textBox.setRight(dotBox.left() - DOT_MARGIN);
+ painter->drawText(textBox, Qt::AlignVCenter | Qt::AlignRight, label);
+
+ painter->setPen(Qt::NoPen);
+ QRect dot(dotBox.left() + DOT_MARGIN,
+ dotBox.center().y() - (DOT_SIZE / 2),
+ DOT_SIZE,
+ DOT_SIZE);
+ for (int i=0; i<5; ++i) {
+ painter->setBrush((i < value) ? QColor(0x3f, 0x3f, 0x3f) : QColor(0xaf, 0xaf, 0xaf));
+ painter->drawEllipse(dot);
+ dot.moveLeft(dot.right() + DOT_MARGIN);
+ }
+}
--- /dev/null
+// AircraftItemDelegate.hxx - part of GUI launcher using Qt5
+//
+// Written by James Turner, started March 2015.
+//
+// Copyright (C) 2014 James Turner <zakalawe@mac.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+#ifndef FG_GUI_AIRCRAFT_ITEM_DELEGATE
+#define FG_GUI_AIRCRAFT_ITEM_DELEGATE
+
+#include <QStyledItemDelegate>
+
+class QListView;
+
+class AircraftItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+public:
+ static const int MARGIN = 4;
+ static const int ARROW_SIZE = 20;
+
+ AircraftItemDelegate(QListView* view);
+
+ virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
+
+ virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const;
+
+ virtual bool eventFilter( QObject*, QEvent* event );
+
+Q_SIGNALS:
+ void variantChanged(const QModelIndex& index);
+
+private:
+ QRect leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const;
+ QRect rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const;
+
+ void drawRating(QPainter* painter, QString label, const QRect& box, int value) const;
+
+ QListView* m_view;
+ QPixmap m_leftArrowIcon,
+ m_rightArrowIcon;
+};
+
+#endif
--- /dev/null
+// AircraftModel.cxx - part of GUI launcher using Qt5
+//
+// Written by James Turner, started March 2015.
+//
+// Copyright (C) 2014 James Turner <zakalawe@mac.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+#include "AircraftModel.hxx"
+
+#include <QDir>
+#include <QThread>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QDataStream>
+#include <QSettings>
+#include <QDebug>
+
+// Simgear
+#include <simgear/props/props_io.hxx>
+#include <simgear/structure/exception.hxx>
+#include <simgear/misc/sg_path.hxx>
+
+// FlightGear
+#include <Main/globals.hxx>
+
+AircraftItem::AircraftItem()
+{
+ // oh for C++11 initialisers
+ for (int i=0; i<4; ++i) ratings[i] = 0;
+}
+
+AircraftItem::AircraftItem(QDir dir, QString filePath)
+{
+ for (int i=0; i<4; ++i) ratings[i] = 0;
+
+ SGPropertyNode root;
+ readProperties(filePath.toStdString(), &root);
+
+ if (!root.hasChild("sim")) {
+ throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
+ }
+
+ SGPropertyNode_ptr sim = root.getNode("sim");
+
+ path = filePath;
+ pathModTime = QFileInfo(path).lastModified();
+
+ description = sim->getStringValue("description");
+ authors = sim->getStringValue("author");
+
+ if (sim->hasChild("rating")) {
+ SGPropertyNode_ptr ratingsNode = sim->getNode("rating");
+ ratings[0] = ratingsNode->getIntValue("FDM");
+ ratings[1] = ratingsNode->getIntValue("systems");
+ ratings[2] = ratingsNode->getIntValue("cockpit");
+ ratings[3] = ratingsNode->getIntValue("model");
+
+ }
+
+ if (sim->hasChild("variant-of")) {
+ variantOf = sim->getStringValue("variant-of");
+ }
+}
+
+QString AircraftItem::baseName() const
+{
+ QString fn = QFileInfo(path).fileName();
+ fn.truncate(fn.count() - 8);
+ return fn;
+}
+
+void AircraftItem::fromDataStream(QDataStream& ds)
+{
+ ds >> path >> description >> authors >> variantOf;
+ for (int i=0; i<4; ++i) ds >> ratings[i];
+ ds >> pathModTime;
+}
+
+void AircraftItem::toDataStream(QDataStream& ds) const
+{
+ ds << path << description << authors << variantOf;
+ for (int i=0; i<4; ++i) ds << ratings[i];
+ ds << pathModTime;
+}
+
+QPixmap AircraftItem::thumbnail() const
+{
+ if (m_thumbnail.isNull()) {
+ QFileInfo info(path);
+ QDir dir = info.dir();
+ 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);
+ }
+ }
+ }
+
+ return m_thumbnail;
+}
+
+
+static int CACHE_VERSION = 2;
+
+class AircraftScanThread : public QThread
+{
+ Q_OBJECT
+public:
+ AircraftScanThread(QStringList dirsToScan) :
+ m_dirs(dirsToScan),
+ m_done(false)
+ {
+ }
+
+ ~AircraftScanThread()
+ {
+ }
+
+ /** thread-safe access to items already scanned */
+ QList<AircraftItem*> items()
+ {
+ QList<AircraftItem*> result;
+ QMutexLocker g(&m_lock);
+ result.swap(m_items);
+ g.unlock();
+ return result;
+ }
+
+ void setDone()
+ {
+ m_done = true;
+ }
+Q_SIGNALS:
+ void addedItems();
+
+protected:
+ virtual void run()
+ {
+ readCache();
+
+ Q_FOREACH(QString d, m_dirs) {
+ scanAircraftDir(QDir(d));
+ if (m_done) {
+ return;
+ }
+ }
+
+ writeCache();
+ }
+
+private:
+ void readCache()
+ {
+ QSettings settings;
+ QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
+ if (!cacheData.isEmpty()) {
+ QDataStream ds(cacheData);
+ quint32 count, cacheVersion;
+ ds >> cacheVersion >> count;
+
+ if (cacheVersion != CACHE_VERSION) {
+ return; // mis-matched cache, version, drop
+ }
+
+ for (int i=0; i<count; ++i) {
+ AircraftItem* item = new AircraftItem;
+ item->fromDataStream(ds);
+
+ QFileInfo finfo(item->path);
+ if (!finfo.exists() || (finfo.lastModified() != item->pathModTime)) {
+ delete item;
+ } else {
+ // corresponding -set.xml file still exists and is
+ // unmodified
+ m_cachedItems[item->path] = item;
+ }
+ } // of cached item iteration
+ }
+ }
+
+ void writeCache()
+ {
+ QSettings settings;
+ QByteArray cacheData;
+ {
+ QDataStream ds(&cacheData, QIODevice::WriteOnly);
+ quint32 count = m_nextCache.count();
+ ds << CACHE_VERSION << count;
+
+ Q_FOREACH(AircraftItem* item, m_nextCache.values()) {
+ item->toDataStream(ds);
+ }
+ }
+
+ settings.setValue("aircraft-cache", cacheData);
+ }
+
+ void scanAircraftDir(QDir path)
+ {
+ QTime t;
+ t.start();
+
+ QStringList filters;
+ filters << "*-set.xml";
+ Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
+ QDir childDir(child.absoluteFilePath());
+ QMap<QString, AircraftItem*> baseAircraft;
+ QList<AircraftItem*> variants;
+
+ Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
+ try {
+ QString absolutePath = xmlChild.absoluteFilePath();
+ AircraftItem* item = NULL;
+
+ if (m_cachedItems.contains(absolutePath)) {
+ item = m_cachedItems.value(absolutePath);
+ } else {
+ item = new AircraftItem(childDir, absolutePath);
+ }
+
+ m_nextCache[absolutePath] = item;
+
+ if (item->variantOf.isNull()) {
+ baseAircraft.insert(item->baseName(), item);
+ } else {
+ variants.append(item);
+ }
+ } catch (sg_exception& e) {
+ continue;
+ }
+
+ if (m_done) {
+ return;
+ }
+ } // of set.xml iteration
+
+ // bind variants to their principals
+ Q_FOREACH(AircraftItem* item, variants) {
+ if (!baseAircraft.contains(item->variantOf)) {
+ qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
+ delete item;
+ continue;
+ }
+
+ baseAircraft.value(item->variantOf)->variants.append(item);
+ }
+
+ // lock mutex while we modify the items array
+ {
+ QMutexLocker g(&m_lock);
+ m_items.append(baseAircraft.values());
+ }
+
+ emit addedItems();
+ } // of subdir iteration
+
+ qDebug() << "scan of" << path << "took" << t.elapsed();
+ }
+
+ QMutex m_lock;
+ QStringList m_dirs;
+ QList<AircraftItem*> m_items;
+
+ QMap<QString, AircraftItem* > m_cachedItems;
+ QMap<QString, AircraftItem* > m_nextCache;
+
+ bool m_done;
+};
+
+AircraftItemModel::AircraftItemModel(QObject* pr) :
+ QAbstractListModel(pr)
+{
+ QStringList dirs;
+ Q_FOREACH(std::string ap, globals->get_aircraft_paths()) {
+ dirs << QString::fromStdString(ap);
+ }
+
+ SGPath rootAircraft(globals->get_fg_root());
+ rootAircraft.append("Aircraft");
+ dirs << QString::fromStdString(rootAircraft.str());
+
+ m_scanThread = new AircraftScanThread(dirs);
+ connect(m_scanThread, &AircraftScanThread::finished, this,
+ &AircraftItemModel::onScanFinished);
+ connect(m_scanThread, &AircraftScanThread::addedItems,
+ this, &AircraftItemModel::onScanResults);
+ m_scanThread->start();
+}
+
+AircraftItemModel::~AircraftItemModel()
+{
+ if (m_scanThread) {
+ m_scanThread->setDone();
+ m_scanThread->wait(1000);
+ delete m_scanThread;
+ }
+}
+
+QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
+ {
+ if (role == AircraftVariantRole) {
+ return m_activeVariant.at(index.row());
+ }
+
+ const AircraftItem* item(m_items.at(index.row()));
+
+ if (role == AircraftVariantCountRole) {
+ return item->variants.count();
+ }
+
+ if (role >= AircraftVariantDescriptionRole) {
+ int variantIndex = role - AircraftVariantDescriptionRole;
+ return item->variants.at(variantIndex)->description;
+ }
+
+ quint32 variantIndex = m_activeVariant.at(index.row());
+ if (variantIndex) {
+ if (variantIndex <= item->variants.count()) {
+ // show the selected variant
+ item = item->variants.at(variantIndex - 1);
+ }
+ }
+
+ if (role == Qt::DisplayRole) {
+ return item->description;
+ } else if (role == Qt::DecorationRole) {
+ return item->thumbnail();
+ } else if (role == AircraftPathRole) {
+ return item->path;
+ } else if (role == AircraftAuthorsRole) {
+ return item->authors;
+ } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
+ return item->ratings[role - AircraftRatingRole];
+ } else if (role == Qt::ToolTipRole) {
+ return item->path;
+ }
+
+ return QVariant();
+ }
+
+bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
+ {
+ if (role == AircraftVariantRole) {
+ m_activeVariant[index.row()] = value.toInt();
+ emit dataChanged(index, index);
+ return true;
+ }
+
+ return false;
+ }
+
+QModelIndex AircraftItemModel::indexOfAircraftPath(QString path) const
+{
+ for (int row=0; row <m_items.size(); ++row) {
+ const AircraftItem* item(m_items.at(row));
+ if (item->path == path) {
+ return index(row);
+ }
+ }
+
+ return QModelIndex();
+}
+
+void AircraftItemModel::onScanResults()
+{
+ QList<AircraftItem*> newItems = m_scanThread->items();
+ if (newItems.isEmpty())
+ return;
+
+ int firstRow = m_items.count();
+ int lastRow = firstRow + newItems.count() - 1;
+ beginInsertRows(QModelIndex(), firstRow, lastRow);
+ m_items.append(newItems);
+
+ // default variants in all cases
+ for (int i=0; i< newItems.count(); ++i) {
+ m_activeVariant.append(0);
+ }
+ endInsertRows();
+}
+
+void AircraftItemModel::onScanFinished()
+{
+ delete m_scanThread;
+ m_scanThread = NULL;
+}
+
+#include "AircraftModel.moc"
--- /dev/null
+// AircraftModel.hxx - part of GUI launcher using Qt5
+//
+// Written by James Turner, started March 2015.
+//
+// Copyright (C) 2014 James Turner <zakalawe@mac.com>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+#ifndef FG_GUI_AIRCRAFT_MODEL
+#define FG_GUI_AIRCRAFT_MODEL
+
+#include <QThread>
+#include <QAbstractListModel>
+#include <QDateTime>
+#include <QDir>
+#include <QPixmap>
+
+const int AircraftPathRole = Qt::UserRole + 1;
+const int AircraftAuthorsRole = Qt::UserRole + 2;
+const int AircraftVariantRole = Qt::UserRole + 3;
+const int AircraftVariantCountRole = Qt::UserRole + 4;
+const int AircraftRatingRole = Qt::UserRole + 100;
+const int AircraftVariantDescriptionRole = Qt::UserRole + 200;
+
+class AircraftScanThread;
+class QDataStream;
+
+struct AircraftItem
+{
+ AircraftItem();
+
+ AircraftItem(QDir dir, QString filePath);
+
+ // the file-name without -set.xml suffix
+ QString baseName() const;
+
+ void fromDataStream(QDataStream& ds);
+
+ void toDataStream(QDataStream& ds) const;
+
+ QPixmap thumbnail() const;
+
+ QString path;
+ QString description;
+ QString authors;
+ int ratings[4];
+ QString variantOf;
+ QDateTime pathModTime;
+
+ QList<AircraftItem*> variants;
+private:
+ mutable QPixmap m_thumbnail;
+};
+
+class AircraftItemModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ AircraftItemModel(QObject* pr);
+
+ ~AircraftItemModel();
+
+ virtual int rowCount(const QModelIndex& parent) const
+ {
+ return m_items.size();
+ }
+
+ virtual QVariant data(const QModelIndex& index, int role) const;
+
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
+
+ QModelIndex indexOfAircraftPath(QString path) const;
+
+private slots:
+ void onScanResults();
+
+ void onScanFinished();
+
+private:
+ AircraftScanThread* m_scanThread;
+ QList<AircraftItem*> m_items;
+ QList<quint32> m_activeVariant;
+};
+
+#endif // of FG_GUI_AIRCRAFT_MODEL
EditRatingsFilterDialog.hxx
SetupRootDialog.cxx
SetupRootDialog.hxx
+ AircraftItemDelegate.hxx
+ AircraftItemDelegate.cxx
+ AircraftModel.hxx
+ AircraftModel.cxx
${uic_sources}
${qrc_sources})
#include <QTimer>
#include <QDebug>
#include <QCompleter>
-#include <QThread>
-#include <QMutex>
-#include <QMutexLocker>
#include <QListView>
#include <QSettings>
-#include <QPainter>
#include <QSortFilterProxyModel>
#include <QMenu>
#include <QDesktopServices>
#include <QUrl>
#include <QAction>
-#include <QStyledItemDelegate>
-#include <QLinearGradient>
#include <QFileDialog>
#include <QMessageBox>
-#include <QDataStream>
#include <QDateTime>
#include <QApplication>
#include "ui_Launcher.h"
#include "EditRatingsFilterDialog.hxx"
+#include "AircraftItemDelegate.hxx"
+#include "AircraftModel.hxx"
#include <Main/globals.hxx>
#include <Navaids/NavDataCache.hxx>
namespace { // anonymous namespace
-const int AircraftPathRole = Qt::UserRole + 1;
-const int AircraftAuthorsRole = Qt::UserRole + 2;
-const int AircraftVariantRole = Qt::UserRole + 3;
-const int AircraftVariantCountRole = Qt::UserRole + 4;
-const int AircraftRatingRole = Qt::UserRole + 100;
-const int AircraftVariantDescriptionRole = Qt::UserRole + 200;
-
void initNavCache()
{
NavDataCache* cache = NavDataCache::createInstance();
}
}
-struct AircraftItem
-{
- AircraftItem()
- {
- // oh for C++11 initialisers
- for (int i=0; i<4; ++i) ratings[i] = 0;
- }
-
- AircraftItem(QDir dir, QString filePath)
- {
- for (int i=0; i<4; ++i) ratings[i] = 0;
-
- SGPropertyNode root;
- readProperties(filePath.toStdString(), &root);
-
- if (!root.hasChild("sim")) {
- throw sg_io_exception(std::string("Malformed -set.xml file"), filePath.toStdString());
- }
-
- SGPropertyNode_ptr sim = root.getNode("sim");
-
- path = filePath;
- pathModTime = QFileInfo(path).lastModified();
-
- description = sim->getStringValue("description");
- authors = sim->getStringValue("author");
-
- if (sim->hasChild("rating")) {
- parseRatings(sim->getNode("rating"));
- }
-
- if (sim->hasChild("variant-of")) {
- variantOf = sim->getStringValue("variant-of");
- }
- }
-
- // the file-name without -set.xml suffix
- QString baseName() const
- {
- QString fn = QFileInfo(path).fileName();
- fn.truncate(fn.count() - 8);
- return fn;
- }
-
- void fromDataStream(QDataStream& ds)
- {
- ds >> path >> description >> authors >> variantOf;
- for (int i=0; i<4; ++i) ds >> ratings[i];
- ds >> pathModTime;
- }
-
- void toDataStream(QDataStream& ds) const
- {
- ds << path << description << authors << variantOf;
- for (int i=0; i<4; ++i) ds << ratings[i];
- ds << pathModTime;
- }
-
- QPixmap thumbnail() const
- {
- if (m_thumbnail.isNull()) {
- QFileInfo info(path);
- QDir dir = info.dir();
- 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);
- }
- }
- }
-
- return m_thumbnail;
- }
-
- QString path;
- QString description;
- QString authors;
- int ratings[4];
- QString variantOf;
- QDateTime pathModTime;
-
- QList<AircraftItem*> variants;
-private:
- mutable QPixmap m_thumbnail;
-
-
- void parseRatings(SGPropertyNode_ptr ratingsNode)
- {
- ratings[0] = ratingsNode->getIntValue("FDM");
- ratings[1] = ratingsNode->getIntValue("systems");
- ratings[2] = ratingsNode->getIntValue("cockpit");
- ratings[3] = ratingsNode->getIntValue("model");
- }
-};
-
-static int CACHE_VERSION = 2;
-
-class AircraftScanThread : public QThread
-{
- Q_OBJECT
-public:
- AircraftScanThread(QStringList dirsToScan) :
- m_dirs(dirsToScan),
- m_done(false)
- {
- }
-
- ~AircraftScanThread()
- {
- }
-
- /** thread-safe access to items already scanned */
- QList<AircraftItem*> items()
- {
- QList<AircraftItem*> result;
- QMutexLocker g(&m_lock);
- result.swap(m_items);
- g.unlock();
- return result;
- }
-
- void setDone()
- {
- m_done = true;
- }
-Q_SIGNALS:
- void addedItems();
-
-protected:
- virtual void run()
- {
- readCache();
-
- Q_FOREACH(QString d, m_dirs) {
- scanAircraftDir(QDir(d));
- if (m_done) {
- return;
- }
- }
-
- writeCache();
- }
-
-private:
- void readCache()
- {
- QSettings settings;
- QByteArray cacheData = settings.value("aircraft-cache").toByteArray();
- if (!cacheData.isEmpty()) {
- QDataStream ds(cacheData);
- quint32 count, cacheVersion;
- ds >> cacheVersion >> count;
-
- if (cacheVersion != CACHE_VERSION) {
- return; // mis-matched cache, version, drop
- }
-
- for (int i=0; i<count; ++i) {
- AircraftItem* item = new AircraftItem;
- item->fromDataStream(ds);
-
- QFileInfo finfo(item->path);
- if (!finfo.exists() || (finfo.lastModified() != item->pathModTime)) {
- delete item;
- } else {
- // corresponding -set.xml file still exists and is
- // unmodified
- m_cachedItems[item->path] = item;
- }
- } // of cached item iteration
- }
- }
-
- void writeCache()
- {
- QSettings settings;
- QByteArray cacheData;
- {
- QDataStream ds(&cacheData, QIODevice::WriteOnly);
- quint32 count = m_nextCache.count();
- ds << CACHE_VERSION << count;
-
- Q_FOREACH(AircraftItem* item, m_nextCache.values()) {
- item->toDataStream(ds);
- }
- }
-
- settings.setValue("aircraft-cache", cacheData);
- }
-
- void scanAircraftDir(QDir path)
- {
- QTime t;
- t.start();
-
- QStringList filters;
- filters << "*-set.xml";
- Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
- QDir childDir(child.absoluteFilePath());
- QMap<QString, AircraftItem*> baseAircraft;
- QList<AircraftItem*> variants;
-
- Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
- try {
- QString absolutePath = xmlChild.absoluteFilePath();
- AircraftItem* item = NULL;
-
- if (m_cachedItems.contains(absolutePath)) {
- item = m_cachedItems.value(absolutePath);
- } else {
- item = new AircraftItem(childDir, absolutePath);
- }
-
- m_nextCache[absolutePath] = item;
-
- if (item->variantOf.isNull()) {
- baseAircraft.insert(item->baseName(), item);
- } else {
- variants.append(item);
- }
- } catch (sg_exception& e) {
- continue;
- }
-
- if (m_done) {
- return;
- }
- } // of set.xml iteration
-
- // bind variants to their principals
- Q_FOREACH(AircraftItem* item, variants) {
- if (!baseAircraft.contains(item->variantOf)) {
- qWarning() << "can't find principal aircraft " << item->variantOf << " for variant:" << item->path;
- delete item;
- continue;
- }
-
- baseAircraft.value(item->variantOf)->variants.append(item);
- }
-
- // lock mutex while we modify the items array
- {
- QMutexLocker g(&m_lock);
- m_items.append(baseAircraft.values());
- }
-
- emit addedItems();
- } // of subdir iteration
-
- qDebug() << "scan of" << path << "took" << t.elapsed();
- }
-
- QMutex m_lock;
- QStringList m_dirs;
- QList<AircraftItem*> m_items;
-
- QMap<QString, AircraftItem* > m_cachedItems;
- QMap<QString, AircraftItem* > m_nextCache;
-
- bool m_done;
-};
-
-class AircraftItemModel : public QAbstractListModel
-{
- Q_OBJECT
-public:
- AircraftItemModel(QObject* pr) :
- QAbstractListModel(pr)
- {
- QStringList dirs;
- Q_FOREACH(std::string ap, globals->get_aircraft_paths()) {
- dirs << QString::fromStdString(ap);
- }
-
- SGPath rootAircraft(globals->get_fg_root());
- rootAircraft.append("Aircraft");
- dirs << QString::fromStdString(rootAircraft.str());
-
- m_scanThread = new AircraftScanThread(dirs);
- connect(m_scanThread, &AircraftScanThread::finished, this,
- &AircraftItemModel::onScanFinished);
- connect(m_scanThread, &AircraftScanThread::addedItems,
- this, &AircraftItemModel::onScanResults);
- m_scanThread->start();
- }
-
- ~AircraftItemModel()
- {
- if (m_scanThread) {
- m_scanThread->setDone();
- m_scanThread->wait(1000);
- delete m_scanThread;
- }
- }
-
- virtual int rowCount(const QModelIndex& parent) const
- {
- return m_items.size();
- }
-
- virtual QVariant data(const QModelIndex& index, int role) const
- {
- if (role == AircraftVariantRole) {
- return m_activeVariant.at(index.row());
- }
-
- const AircraftItem* item(m_items.at(index.row()));
-
- if (role == AircraftVariantCountRole) {
- return item->variants.count();
- }
-
- if (role >= AircraftVariantDescriptionRole) {
- int variantIndex = role - AircraftVariantDescriptionRole;
- return item->variants.at(variantIndex)->description;
- }
-
- quint32 variantIndex = m_activeVariant.at(index.row());
- if (variantIndex) {
- if (variantIndex <= item->variants.count()) {
- // show the selected variant
- item = item->variants.at(variantIndex - 1);
- }
- }
-
- if (role == Qt::DisplayRole) {
- return item->description;
- } else if (role == Qt::DecorationRole) {
- return item->thumbnail();
- } else if (role == AircraftPathRole) {
- return item->path;
- } else if (role == AircraftAuthorsRole) {
- return item->authors;
- } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
- return item->ratings[role - AircraftRatingRole];
- } else if (role == Qt::ToolTipRole) {
- return item->path;
- }
-
- return QVariant();
- }
-
- virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
- {
- if (role == AircraftVariantRole) {
- m_activeVariant[index.row()] = value.toInt();
- emit dataChanged(index, index);
- return true;
- }
-
- return false;
- }
-
- QModelIndex indexOfAircraftPath(QString path) const
- {
- for (int row=0; row <m_items.size(); ++row) {
- const AircraftItem* item(m_items.at(row));
- if (item->path == path) {
- return index(row);
- }
- }
-
- return QModelIndex();
- }
-
-private slots:
- void onScanResults()
- {
- QList<AircraftItem*> newItems = m_scanThread->items();
- if (newItems.isEmpty())
- return;
-
- int firstRow = m_items.count();
- int lastRow = firstRow + newItems.count() - 1;
- beginInsertRows(QModelIndex(), firstRow, lastRow);
- m_items.append(newItems);
-
- // default variants in all cases
- for (int i=0; i< newItems.count(); ++i) {
- m_activeVariant.append(0);
- }
- endInsertRows();
- }
-
- void onScanFinished()
- {
- delete m_scanThread;
- m_scanThread = NULL;
- }
-
-private:
- AircraftScanThread* m_scanThread;
- QList<AircraftItem*> m_items;
- QList<quint32> m_activeVariant;
-};
-
-class AircraftItemDelegate : public QStyledItemDelegate
-{
- Q_OBJECT
-public:
- static const int MARGIN = 4;
- static const int ARROW_SIZE = 20;
-
- AircraftItemDelegate(QListView* view) :
- m_view(view)
- {
- view->viewport()->installEventFilter(this);
-
- m_leftArrowIcon.load(":/left-arrow-icon");
- m_rightArrowIcon.load(":/right-arrow-icon");
- }
-
- virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
- {
- // selection feedback rendering
- if (option.state & QStyle::State_Selected) {
- QLinearGradient grad(option.rect.topLeft(), option.rect.bottomLeft());
- grad.setColorAt(0.0, QColor(152, 163, 180));
- grad.setColorAt(1.0, QColor(90, 107, 131));
-
- QBrush backgroundBrush(grad);
- painter->fillRect(option.rect, backgroundBrush);
-
- painter->setPen(QColor(90, 107, 131));
- painter->drawLine(option.rect.topLeft(), option.rect.topRight());
-
- }
-
- QRect contentRect = option.rect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
-
- QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
- painter->drawPixmap(contentRect.topLeft(), thumbnail);
-
- // draw 1px frame
- painter->setPen(QColor(0x7f, 0x7f, 0x7f));
- painter->setBrush(Qt::NoBrush);
- painter->drawRect(contentRect.left(), contentRect.top(), thumbnail.width(), thumbnail.height());
-
- int variantCount = index.data(AircraftVariantCountRole).toInt();
- int currentVariant =index.data(AircraftVariantRole).toInt();
- QString description = index.data(Qt::DisplayRole).toString();
- contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
-
- painter->setPen(Qt::black);
- QFont f;
- f.setPointSize(18);
- painter->setFont(f);
-
- QRect descriptionRect = contentRect.adjusted(ARROW_SIZE, 0, -ARROW_SIZE, 0),
- actualBounds;
-
- if (variantCount > 0) {
- bool canLeft = (currentVariant > 0);
- bool canRight = (currentVariant < variantCount );
-
- QRect leftArrowRect = leftCycleArrowRect(option.rect, index);
- if (canLeft) {
- painter->drawPixmap(leftArrowRect.topLeft() + QPoint(2, 2), m_leftArrowIcon);
- }
-
- QRect rightArrowRect = rightCycleArrowRect(option.rect, index);
- if (canRight) {
- painter->drawPixmap(rightArrowRect.topLeft() + QPoint(2, 2), m_rightArrowIcon);
- }
- }
-
- painter->drawText(descriptionRect, Qt::TextWordWrap, description, &actualBounds);
-
- QString authors = index.data(AircraftAuthorsRole).toString();
-
- f.setPointSize(12);
- painter->setFont(f);
-
- QRect authorsRect = descriptionRect;
- authorsRect.moveTop(actualBounds.bottom() + MARGIN);
- painter->drawText(authorsRect, Qt::TextWordWrap,
- QString("by: %1").arg(authors),
- &actualBounds);
-
- QRect r = contentRect;
- r.setWidth(contentRect.width() / 2);
- r.moveTop(actualBounds.bottom() + MARGIN);
- r.setHeight(24);
-
- drawRating(painter, "Flight model:", r, index.data(AircraftRatingRole).toInt());
- r.moveTop(r.bottom());
- drawRating(painter, "Systems:", r, index.data(AircraftRatingRole + 1).toInt());
-
- r.moveTop(actualBounds.bottom() + MARGIN);
- r.moveLeft(r.right());
- drawRating(painter, "Cockpit:", r, index.data(AircraftRatingRole + 2).toInt());
- r.moveTop(r.bottom());
- drawRating(painter, "Exterior model:", r, index.data(AircraftRatingRole + 3).toInt());
- }
-
- virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
- {
- return QSize(500, 128 + (MARGIN * 2));
- }
-
- virtual bool eventFilter( QObject*, QEvent* event )
- {
- if ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
- {
- QMouseEvent* me = static_cast< QMouseEvent* >( event );
- QModelIndex index = m_view->indexAt( me->pos() );
- int variantCount = index.data(AircraftVariantCountRole).toInt();
- int variantIndex = index.data(AircraftVariantRole).toInt();
-
- if ( (event->type() == QEvent::MouseButtonRelease) && (variantCount > 0) )
- {
- QRect vr = m_view->visualRect(index);
- QRect leftCycleRect = leftCycleArrowRect(vr, index),
- rightCycleRect = rightCycleArrowRect(vr, index);
-
- if ((variantIndex > 0) && leftCycleRect.contains(me->pos())) {
- m_view->model()->setData(index, variantIndex - 1, AircraftVariantRole);
- emit variantChanged(index);
- return true;
- } else if ((variantIndex < variantCount) && rightCycleRect.contains(me->pos())) {
- m_view->model()->setData(index, variantIndex + 1, AircraftVariantRole);
- emit variantChanged(index);
- return true;
- }
- }
- } // of mouse button press or release
-
- return false;
- }
-
-Q_SIGNALS:
- void variantChanged(const QModelIndex& index);
-
-private:
- QRect leftCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
- {
- QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
- QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
- contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
-
- QRect r = contentRect;
- r.setRight(r.left() + ARROW_SIZE);
- r.setBottom(r.top() + ARROW_SIZE);
- return r;
-
- }
-
- QRect rightCycleArrowRect(const QRect& visualRect, const QModelIndex& index) const
- {
- QRect contentRect = visualRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN);
- QPixmap thumbnail = index.data(Qt::DecorationRole).value<QPixmap>();
- contentRect.setLeft(contentRect.left() + MARGIN + thumbnail.width());
-
- QRect r = contentRect;
- r.setLeft(r.right() - ARROW_SIZE);
- r.setBottom(r.top() + ARROW_SIZE);
- return r;
-
- }
-
- void drawRating(QPainter* painter, QString label, const QRect& box, int value) const
- {
- const int DOT_SIZE = 10;
- const int DOT_MARGIN = 4;
-
- QRect dotBox = box;
- dotBox.setLeft(box.right() - (DOT_MARGIN * 6 + DOT_SIZE * 5));
-
- painter->setPen(Qt::black);
- QRect textBox = box;
- textBox.setRight(dotBox.left() - DOT_MARGIN);
- painter->drawText(textBox, Qt::AlignVCenter | Qt::AlignRight, label);
-
- painter->setPen(Qt::NoPen);
- QRect dot(dotBox.left() + DOT_MARGIN,
- dotBox.center().y() - (DOT_SIZE / 2),
- DOT_SIZE,
- DOT_SIZE);
- for (int i=0; i<5; ++i) {
- painter->setBrush((i < value) ? QColor(0x3f, 0x3f, 0x3f) : QColor(0xaf, 0xaf, 0xaf));
- painter->drawEllipse(dot);
- dot.moveLeft(dot.right() + DOT_MARGIN);
- }
- }
-
- QListView* m_view;
- QPixmap m_leftArrowIcon,
- m_rightArrowIcon;
-};
-
class ArgumentsTokenizer
{
public: