From: James Turner Date: Tue, 10 Mar 2015 00:13:55 +0000 (+0000) Subject: Refactor aircraft helper classes X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=92864265af49c482b58f26ec78fe10a358ea561f;p=flightgear.git Refactor aircraft helper classes - move the aircraft list and delegate to their own files --- diff --git a/src/GUI/AircraftItemDelegate.cxx b/src/GUI/AircraftItemDelegate.cxx new file mode 100644 index 000000000..0dceacb32 --- /dev/null +++ b/src/GUI/AircraftItemDelegate.cxx @@ -0,0 +1,210 @@ +// AircraftItemDelegate.cxx - part of GUI launcher using Qt5 +// +// Written by James Turner, started March 2015. +// +// Copyright (C) 2014 James Turner +// +// 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 +#include +#include +#include +#include + +#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(); + 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(); + 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(); + 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); + } +} diff --git a/src/GUI/AircraftItemDelegate.hxx b/src/GUI/AircraftItemDelegate.hxx new file mode 100644 index 000000000..e52603b15 --- /dev/null +++ b/src/GUI/AircraftItemDelegate.hxx @@ -0,0 +1,57 @@ +// AircraftItemDelegate.hxx - part of GUI launcher using Qt5 +// +// Written by James Turner, started March 2015. +// +// Copyright (C) 2014 James Turner +// +// 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 + +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 diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx new file mode 100644 index 000000000..c39c40023 --- /dev/null +++ b/src/GUI/AircraftModel.cxx @@ -0,0 +1,402 @@ +// AircraftModel.cxx - part of GUI launcher using Qt5 +// +// Written by James Turner, started March 2015. +// +// Copyright (C) 2014 James Turner +// +// 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 +#include +#include +#include +#include +#include +#include + +// Simgear +#include +#include +#include + +// FlightGear +#include
+ +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 items() + { + QList 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; ifromDataStream(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 baseAircraft; + QList 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 m_items; + + QMap m_cachedItems; + QMap 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 path == path) { + return index(row); + } + } + + return QModelIndex(); +} + +void AircraftItemModel::onScanResults() +{ + QList 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" diff --git a/src/GUI/AircraftModel.hxx b/src/GUI/AircraftModel.hxx new file mode 100644 index 000000000..894a4f315 --- /dev/null +++ b/src/GUI/AircraftModel.hxx @@ -0,0 +1,97 @@ +// AircraftModel.hxx - part of GUI launcher using Qt5 +// +// Written by James Turner, started March 2015. +// +// Copyright (C) 2014 James Turner +// +// 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 +#include +#include +#include +#include + +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 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 m_items; + QList m_activeVariant; +}; + +#endif // of FG_GUI_AIRCRAFT_MODEL diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index 83da4b767..8bf480b35 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -83,6 +83,10 @@ if (HAVE_QT) EditRatingsFilterDialog.hxx SetupRootDialog.cxx SetupRootDialog.hxx + AircraftItemDelegate.hxx + AircraftItemDelegate.cxx + AircraftModel.hxx + AircraftModel.cxx ${uic_sources} ${qrc_sources}) diff --git a/src/GUI/QtLauncher.cxx b/src/GUI/QtLauncher.cxx index 27fd59c20..1bd515e3c 100644 --- a/src/GUI/QtLauncher.cxx +++ b/src/GUI/QtLauncher.cxx @@ -30,22 +30,15 @@ #include #include #include -#include -#include -#include #include #include -#include #include #include #include #include #include -#include -#include #include #include -#include #include #include @@ -57,6 +50,8 @@ #include "ui_Launcher.h" #include "EditRatingsFilterDialog.hxx" +#include "AircraftItemDelegate.hxx" +#include "AircraftModel.hxx" #include
#include @@ -72,13 +67,6 @@ const int MAX_RECENT_AIRCRAFT = 20; 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(); @@ -98,597 +86,6 @@ void initNavCache() } } -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 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 items() - { - QList 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; ifromDataStream(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 baseAircraft; - QList 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 m_items; - - QMap m_cachedItems; - QMap 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 path == path) { - return index(row); - } - } - - return QModelIndex(); - } - -private slots: - void onScanResults() - { - QList 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 m_items; - QList 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(); - 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(); - 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(); - 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: