]> git.mxchange.org Git - flightgear.git/commitdiff
Refactor aircraft helper classes
authorJames Turner <zakalawe@mac.com>
Tue, 10 Mar 2015 00:13:55 +0000 (00:13 +0000)
committerJames Turner <zakalawe@mac.com>
Wed, 11 Mar 2015 17:09:59 +0000 (17:09 +0000)
- move the aircraft list and delegate to their own files

src/GUI/AircraftItemDelegate.cxx [new file with mode: 0644]
src/GUI/AircraftItemDelegate.hxx [new file with mode: 0644]
src/GUI/AircraftModel.cxx [new file with mode: 0644]
src/GUI/AircraftModel.hxx [new file with mode: 0644]
src/GUI/CMakeLists.txt
src/GUI/QtLauncher.cxx

diff --git a/src/GUI/AircraftItemDelegate.cxx b/src/GUI/AircraftItemDelegate.cxx
new file mode 100644 (file)
index 0000000..0dceacb
--- /dev/null
@@ -0,0 +1,210 @@
+// 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);
+    }
+}
diff --git a/src/GUI/AircraftItemDelegate.hxx b/src/GUI/AircraftItemDelegate.hxx
new file mode 100644 (file)
index 0000000..e52603b
--- /dev/null
@@ -0,0 +1,57 @@
+// 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
diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx
new file mode 100644 (file)
index 0000000..c39c400
--- /dev/null
@@ -0,0 +1,402 @@
+// 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"
diff --git a/src/GUI/AircraftModel.hxx b/src/GUI/AircraftModel.hxx
new file mode 100644 (file)
index 0000000..894a4f3
--- /dev/null
@@ -0,0 +1,97 @@
+// 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
index 83da4b767662b2676904d149ab7332d36759c182..8bf480b351a5f10e9ce12cd1b4182f17138e1642 100644 (file)
@@ -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})
 
index 27fd59c20cacdda428712522cf5c004684cca7be..1bd515e3c7aad341c1f47b9aec84283433425a9e 100644 (file)
 #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>
 
@@ -57,6 +50,8 @@
 
 #include "ui_Launcher.h"
 #include "EditRatingsFilterDialog.hxx"
+#include "AircraftItemDelegate.hxx"
+#include "AircraftModel.hxx"
 
 #include <Main/globals.hxx>
 #include <Navaids/NavDataCache.hxx>
@@ -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<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: