]> git.mxchange.org Git - flightgear.git/commitdiff
In-app launcher for Mac, based on Qt5.
authorJames Turner <zakalawe@mac.com>
Fri, 26 Dec 2014 12:20:51 +0000 (15:20 +0300)
committerJames Turner <zakalawe@mac.com>
Tue, 6 Jan 2015 19:13:30 +0000 (19:13 +0000)
The old Mac launcher doesn’t work on Yosemite, add a tiny
Qt-based launcher inside the main process (no need to fork /
exec) which runs before the OSG window is created.

Will be merged for 3.4, hopefully with no impact on other
platforms.

23 files changed:
CMakeLists.txt
src/GUI/AirportDiagram.cxx [new file with mode: 0644]
src/GUI/AirportDiagram.hxx [new file with mode: 0644]
src/GUI/CMakeLists.txt
src/GUI/EditRatingsFilterDialog.cxx [new file with mode: 0644]
src/GUI/EditRatingsFilterDialog.hxx [new file with mode: 0644]
src/GUI/EditRatingsFilterDialog.ui [new file with mode: 0644]
src/GUI/Launcher.ui [new file with mode: 0644]
src/GUI/QtLauncher.cxx [new file with mode: 0644]
src/GUI/QtLauncher.hxx [new file with mode: 0644]
src/GUI/history-icon.png [new file with mode: 0644]
src/GUI/large-search-icon.png [new file with mode: 0644]
src/GUI/resources.qrc [new file with mode: 0644]
src/Include/config_cmake.h.in
src/Main/CMakeLists.txt
src/Main/fg_init.cxx
src/Main/fg_init.hxx
src/Main/main.cxx
src/Main/options.cxx
src/Main/options.hxx
src/Navaids/NavDataCache.cxx
src/Navaids/NavDataCache.hxx
src/Viewer/WindowBuilder.cxx

index bab8468189f3bfedfb294f1e3971cb60df987115..f2ab94ff0fbd8597b5d176deca77c81ec49631f3 100644 (file)
@@ -277,6 +277,16 @@ endif (USE_DBUS)
 # Sqlite always depends on the threading lib
 list(APPEND SQLITE3_LIBRARY ${CMAKE_THREAD_LIBS_INIT})
 
+##############################################################################
+## Qt5 setup setup
+
+find_package(Qt5 5.1 COMPONENTS Widgets)
+if (Qt5Widgets_FOUND)
+    message(STATUS "Will enable Qt launcher GUI")
+    set(HAVE_QT 1)
+    set(CMAKE_AUTOMOC ON)
+endif()
+
 ##############################################################################
 
 find_package(PLIB REQUIRED puaux pu js fnt)
diff --git a/src/GUI/AirportDiagram.cxx b/src/GUI/AirportDiagram.cxx
new file mode 100644 (file)
index 0000000..68acba5
--- /dev/null
@@ -0,0 +1,264 @@
+#include "AirportDiagram.hxx"
+
+#include <QPainter>
+#include <QDebug>
+
+#include <Airports/airport.hxx>
+#include <Airports/runways.hxx>
+#include <Airports/parking.hxx>
+#include <Airports/pavement.hxx>
+
+/* equatorial and polar earth radius */
+const float rec  = 6378137;          // earth radius, equator (?)
+const float rpol = 6356752.314f;      // earth radius, polar   (?)
+
+//Returns Earth radius at a given latitude (Ellipsoide equation with two equal axis)
+static float earth_radius_lat( float lat )
+{
+    double a = cos(lat)/rec;
+    double b = sin(lat)/rpol;
+    return 1.0f / sqrt( a * a + b * b );
+}
+
+
+AirportDiagram::AirportDiagram(QWidget* pr) :
+QWidget(pr)
+{
+    setSizePolicy(QSizePolicy::MinimumExpanding,
+                  QSizePolicy::MinimumExpanding);
+    setMinimumSize(100, 100);
+}
+
+void AirportDiagram::setAirport(FGAirportRef apt)
+{
+    m_airport = apt;
+    m_projectionCenter = apt ? apt->geod() : SGGeod();
+    m_scale = 1.0;
+    m_bounds = QRectF(); // clear
+    m_runways.clear();
+
+    if (apt) {
+        buildTaxiways();
+        buildPavements();
+    }
+    
+    update();
+}
+
+void AirportDiagram::addRunway(FGRunwayRef rwy)
+{
+    Q_FOREACH(RunwayData rd, m_runways) {
+        if (rd.runway == rwy->reciprocalRunway()) {
+            return; // only add one end of reciprocal runways
+        }
+    }
+
+    QPointF p1 = project(rwy->geod()),
+    p2 = project(rwy->end());
+    extendBounds(p1);
+    extendBounds(p2);
+
+    RunwayData r;
+    r.p1 = p1;
+    r.p2 = p2;
+    r.widthM = qRound(rwy->widthM());
+    r.runway = rwy;
+    m_runways.append(r);
+    update();
+}
+
+void AirportDiagram::addParking(FGParking* park)
+{
+    QPointF p = project(park->geod());
+    extendBounds(p);
+    update();
+}
+
+void AirportDiagram::paintEvent(QPaintEvent* pe)
+{
+    QPainter p(this);
+    p.fillRect(rect(), QColor(0x3f, 0x3f, 0x3f));
+
+    // fit bounds within our available space, allowing for a margin
+    const int MARGIN = 32; // pixels
+    double ratioInX = (width() - MARGIN * 2) / m_bounds.width();
+    double ratioInY = (height() - MARGIN * 2) / m_bounds.height();
+    double scale = std::min(ratioInX, ratioInY);
+
+    QTransform t;
+    t.translate(width() / 2, height() / 2); // center projection origin in the widget
+    t.scale(scale, scale);
+    // center the bounding box (may not be at the origin)
+    t.translate(-m_bounds.center().x(), -m_bounds.center().y());
+    p.setTransform(t);
+
+// pavements
+    QBrush brush(QColor(0x9f, 0x9f, 0x9f));
+    Q_FOREACH(const QPainterPath& path, m_pavements) {
+        p.drawPath(path);
+    }
+
+// taxiways
+    Q_FOREACH(const TaxiwayData& t, m_taxiways) {
+        QPen pen(QColor(0x9f, 0x9f, 0x9f));
+        pen.setWidth(t.widthM);
+        p.setPen(pen);
+        p.drawLine(t.p1, t.p2);
+    }
+
+// runways
+    QPen pen(Qt::magenta);
+    QFont f;
+    f.setPixelSize(14);
+    p.setFont(f);
+
+    Q_FOREACH(const RunwayData& r, m_runways) {
+        p.setTransform(t);
+
+        pen.setWidth(r.widthM);
+        p.setPen(pen);
+        p.drawLine(r.p1, r.p2);
+
+    // draw idents
+        QString ident = QString::fromStdString(r.runway->ident());
+
+        p.translate(r.p1);
+        p.rotate(r.runway->headingDeg());
+        // invert scaling factor so we can use screen pixel sizes here
+        p.scale(1.0 / scale, 1.0/ scale);
+
+        p.drawText(QRect(-100, 5, 200, 200), ident, Qt::AlignHCenter | Qt::AlignTop);
+
+        FGRunway* recip = r.runway->reciprocalRunway();
+        QString recipIdent = QString::fromStdString(recip->ident());
+
+        p.setTransform(t);
+        p.translate(r.p2);
+        p.rotate(recip->headingDeg());
+        p.scale(1.0 / scale, 1.0/ scale);
+
+        p.drawText(QRect(-100, 5, 200, 200), recipIdent, Qt::AlignHCenter | Qt::AlignTop);
+    }
+}
+
+
+void AirportDiagram::extendBounds(const QPointF& p)
+{
+    if (p.x() < m_bounds.left()) {
+        m_bounds.setLeft(p.x());
+    } else if (p.x() > m_bounds.right()) {
+        m_bounds.setRight(p.x());
+    }
+
+    if (p.y() < m_bounds.top()) {
+        m_bounds.setTop(p.y());
+    } else if (p.y() > m_bounds.bottom()) {
+        m_bounds.setBottom(p.y());
+    }
+}
+
+QPointF AirportDiagram::project(const SGGeod& geod) const
+{
+    double r = earth_radius_lat(geod.getLatitudeRad());
+    double ref_lat = m_projectionCenter.getLatitudeRad(),
+    ref_lon = m_projectionCenter.getLongitudeRad(),
+    lat = geod.getLatitudeRad(),
+    lon = geod.getLongitudeRad(),
+    lonDiff = lon - ref_lon;
+
+    double c = acos( sin(ref_lat) * sin(lat) + cos(ref_lat) * cos(lat) * cos(lonDiff) );
+    if (c == 0.0) {
+        // angular distance from center is 0
+        return QPointF(0.0, 0.0);
+    }
+
+    double k = c / sin(c);
+    double x, y;
+    if (ref_lat == (90 * SG_DEGREES_TO_RADIANS))
+    {
+        x = (SGD_PI / 2 - lat) * sin(lonDiff);
+        y = -(SGD_PI / 2 - lat) * cos(lonDiff);
+    }
+    else if (ref_lat == -(90 * SG_DEGREES_TO_RADIANS))
+    {
+        x = (SGD_PI / 2 + lat) * sin(lonDiff);
+        y = (SGD_PI / 2 + lat) * cos(lonDiff);
+    }
+    else
+    {
+        x = k * cos(lat) * sin(lonDiff);
+        y = k * ( cos(ref_lat) * sin(lat) - sin(ref_lat) * cos(lat) * cos(lonDiff) );
+    }
+
+    return QPointF(x, -y) * r * m_scale;
+}
+
+void AirportDiagram::buildTaxiways()
+{
+    m_taxiways.clear();
+    for (unsigned int tIndex=0; tIndex < m_airport->numTaxiways(); ++tIndex) {
+        FGTaxiwayRef tx = m_airport->getTaxiwayByIndex(tIndex);
+
+        TaxiwayData td;
+        td.p1 = project(tx->geod());
+        td.p2 = project(tx->pointOnCenterline(tx->lengthM()));
+        extendBounds(td.p1);
+        extendBounds(td.p2);
+        td.widthM = tx->widthM();
+        m_taxiways.append(td);
+    }
+}
+
+void AirportDiagram::buildPavements()
+{
+    m_pavements.clear();
+    for (unsigned int pIndex=0; pIndex < m_airport->numPavements(); ++pIndex) {
+        FGPavementRef pave = m_airport->getPavementByIndex(pIndex);
+        if (pave->getNodeList().empty()) {
+            continue;
+        }
+
+        QPainterPath pp;
+        QPointF startPoint;
+        bool closed = true;
+        QPointF p0 = project(pave->getNodeList().front()->mPos);
+
+        FGPavement::NodeList::const_iterator it;
+        for (it = pave->getNodeList().begin(); it != pave->getNodeList().end(); ) {
+            const FGPavement::BezierNode *bn = dynamic_cast<const FGPavement::BezierNode *>(it->get());
+            bool close = (*it)->mClose;
+
+            // increment iterator so we can look at the next point
+            ++it;
+            QPointF nextPoint = (it == pave->getNodeList().end()) ? startPoint : project((*it)->mPos);
+
+            if (bn) {
+                QPointF control = project(bn->mControl);
+                QPointF endPoint = close ? startPoint : nextPoint;
+                pp.quadTo(control, endPoint);
+            } else {
+                // straight line segment
+                if (closed) {
+                    pp.moveTo(p0);
+                    closed = false;
+                    startPoint = p0;
+                } else
+                    pp.lineTo(p0);
+            }
+
+            if (close) {
+                closed = true;
+                pp.closeSubpath();
+                startPoint = QPointF();
+            }
+
+            p0 = nextPoint;
+        } // of nodes iteration
+
+        if (!closed) {
+            pp.closeSubpath();
+        }
+
+        m_pavements.append(pp);
+    } // of pavements iteration
+}
diff --git a/src/GUI/AirportDiagram.hxx b/src/GUI/AirportDiagram.hxx
new file mode 100644 (file)
index 0000000..d6802e5
--- /dev/null
@@ -0,0 +1,55 @@
+#include <QWidget>
+#include <QPainterPath>
+
+#include <Airports/airports_fwd.hxx>
+#include <simgear/math/sg_geodesy.hxx>
+
+class AirportDiagram : public QWidget
+{
+public:
+    AirportDiagram(QWidget* pr);
+
+    void setAirport(FGAirportRef apt);
+
+    void addRunway(FGRunwayRef rwy);
+    void addParking(FGParking* park);
+protected:
+    virtual void paintEvent(QPaintEvent* pe);
+    // wheel event for zoom
+
+    // mouse drag for pan
+
+
+private:
+    void extendBounds(const QPointF& p);
+    QPointF project(const SGGeod& geod) const;
+
+    void buildTaxiways();
+    void buildPavements();
+
+    FGAirportRef m_airport;
+    SGGeod m_projectionCenter;
+    double m_scale;
+    QRectF m_bounds;
+
+    struct RunwayData {
+        QPointF p1, p2;
+        int widthM;
+        FGRunwayRef runway;
+    };
+    
+    QList<RunwayData> m_runways;
+
+    struct TaxiwayData {
+        QPointF p1, p2;
+        int widthM;
+
+        bool operator<(const TaxiwayData& other) const
+        {
+            return widthM < other.widthM;
+        }
+    };
+
+    QList<TaxiwayData> m_taxiways;
+    QList<QPainterPath> m_pavements;
+};
index e51a32679bf7f7fc0861271c2f0efcf2ad42d29c..d31e4f1924c790334ef659e62c966c5802843e07 100644 (file)
@@ -53,17 +53,41 @@ if(WIN32)
                                                FGWindowsMenuBar.cxx
                                                WindowsFileDialog.cxx)
 endif()
-               
+
 if (APPLE)
-    list(APPEND HEADERS FGCocoaMenuBar.hxx 
-        CocoaFileDialog.hxx 
+    list(APPEND HEADERS FGCocoaMenuBar.hxx
+        CocoaFileDialog.hxx
         CocoaMouseCursor.hxx
         CocoaHelpers.h
         CocoaHelpers_private.h)
-    list(APPEND SOURCES FGCocoaMenuBar.mm 
+    list(APPEND SOURCES FGCocoaMenuBar.mm
         CocoaFileDialog.mm
         CocoaMouseCursor.mm
         CocoaHelpers.mm)
 endif()
-               
+
+
+
+
+if (HAVE_QT)
+    qt5_wrap_ui(uic_sources Launcher.ui EditRatingsFilterDialog.ui)
+    qt5_add_resources(qrc_sources resources.qrc)
+
+    include_directories(${PROJECT_BINARY_DIR}/src/GUI)
+
+    add_library(fglauncher QtLauncher.cxx
+                            QtLauncher.hxx
+                            AirportDiagram.cxx
+                            AirportDiagram.hxx
+                            EditRatingsFilterDialog.cxx
+                            EditRatingsFilterDialog.hxx
+                            ${uic_sources}
+                            ${qrc_sources})
+
+    target_link_libraries(fglauncher Qt5::Core Qt5::Widgets )
+
+
+endif()
+
+
 flightgear_component(GUI "${SOURCES}" "${HEADERS}")
diff --git a/src/GUI/EditRatingsFilterDialog.cxx b/src/GUI/EditRatingsFilterDialog.cxx
new file mode 100644 (file)
index 0000000..f6b5bd1
--- /dev/null
@@ -0,0 +1,39 @@
+#include "EditRatingsFilterDialog.hxx"
+#include "ui_EditRatingsFilterDialog.h"
+
+EditRatingsFilterDialog::EditRatingsFilterDialog(QWidget *parent) :
+    QDialog(parent),
+    ui(new Ui::EditRatingsFilterDialog)
+{
+    ui->setupUi(this);
+}
+
+EditRatingsFilterDialog::~EditRatingsFilterDialog()
+{
+    delete ui;
+}
+
+void EditRatingsFilterDialog::setRatings(int *ratings)
+{
+    for (int i=0; i<4; ++i) {
+        QAbstractSlider* s = sliderForIndex(i);
+        s->setValue(ratings[i]);
+    }
+}
+
+int EditRatingsFilterDialog::getRating(int index) const
+{
+    return sliderForIndex(index)->value();
+}
+
+QAbstractSlider* EditRatingsFilterDialog::sliderForIndex(int index) const
+{
+    switch (index) {
+        case 0: return ui->fdmSlider;
+        case 1: return ui->systemsSlider;
+        case 2: return ui->cockpitSlider;
+        case 3: return ui->modelSlider;
+        default:
+            return 0;
+    }
+}
\ No newline at end of file
diff --git a/src/GUI/EditRatingsFilterDialog.hxx b/src/GUI/EditRatingsFilterDialog.hxx
new file mode 100644 (file)
index 0000000..0e0faf4
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef EDITRATINGSFILTERDIALOG_HXX
+#define EDITRATINGSFILTERDIALOG_HXX
+
+#include <QDialog>
+
+namespace Ui {
+class EditRatingsFilterDialog;
+}
+
+class QAbstractSlider;
+
+class EditRatingsFilterDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    explicit EditRatingsFilterDialog(QWidget *parent = 0);
+    ~EditRatingsFilterDialog();
+
+    void setRatings(int* ratings);
+
+    int getRating(int index) const;
+private:
+    Ui::EditRatingsFilterDialog *ui;
+
+    QAbstractSlider* sliderForIndex(int index) const;
+};
+
+#endif // EDITRATINGSFILTERDIALOG_HXX
diff --git a/src/GUI/EditRatingsFilterDialog.ui b/src/GUI/EditRatingsFilterDialog.ui
new file mode 100644 (file)
index 0000000..ed6e698
--- /dev/null
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EditRatingsFilterDialog</class>
+ <widget class="QDialog" name="EditRatingsFilterDialog">
+  <property name="windowModality">
+   <enum>Qt::WindowModal</enum>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>623</width>
+    <height>594</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0" colspan="3">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Specify the minimum required completeness of aircraft in each area for it to be shown in the aircraft list.</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Cockpit:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="2">
+    <widget class="QSlider" name="cockpitSlider">
+     <property name="maximum">
+      <number>5</number>
+     </property>
+     <property name="value">
+      <number>3</number>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="invertedAppearance">
+      <bool>false</bool>
+     </property>
+     <property name="invertedControls">
+      <bool>false</bool>
+     </property>
+     <property name="tickPosition">
+      <enum>QSlider::TicksBelow</enum>
+     </property>
+     <property name="tickInterval">
+      <number>1</number>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>3D panel, cockpit and instrumentation status</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0" colspan="2">
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>Flight model:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="2">
+    <widget class="QSlider" name="fdmSlider">
+     <property name="maximum">
+      <number>5</number>
+     </property>
+     <property name="value">
+      <number>3</number>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="invertedAppearance">
+      <bool>false</bool>
+     </property>
+     <property name="invertedControls">
+      <bool>false</bool>
+     </property>
+     <property name="tickPosition">
+      <enum>QSlider::TicksBelow</enum>
+     </property>
+     <property name="tickInterval">
+      <number>1</number>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="2">
+    <widget class="QLabel" name="label_5">
+     <property name="text">
+      <string>Accuracy of the flight (aerodynamic) model compared to available data and testing/</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="0" colspan="2">
+    <widget class="QLabel" name="label_6">
+     <property name="text">
+      <string>Exterior model:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="2">
+    <widget class="QSlider" name="modelSlider">
+     <property name="maximum">
+      <number>5</number>
+     </property>
+     <property name="value">
+      <number>3</number>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="invertedAppearance">
+      <bool>false</bool>
+     </property>
+     <property name="invertedControls">
+      <bool>false</bool>
+     </property>
+     <property name="tickPosition">
+      <enum>QSlider::TicksBelow</enum>
+     </property>
+     <property name="tickInterval">
+      <number>1</number>
+     </property>
+    </widget>
+   </item>
+   <item row="6" column="2">
+    <widget class="QLabel" name="label_7">
+     <property name="text">
+      <string>Quality and detail of exterior model, including animated moving parts such as control surfaces and under-carriage</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="7" column="0" colspan="2">
+    <widget class="QLabel" name="label_8">
+     <property name="text">
+      <string>Systems:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="7" column="2">
+    <widget class="QSlider" name="systemsSlider">
+     <property name="maximum">
+      <number>5</number>
+     </property>
+     <property name="value">
+      <number>3</number>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="invertedAppearance">
+      <bool>false</bool>
+     </property>
+     <property name="invertedControls">
+      <bool>false</bool>
+     </property>
+     <property name="tickPosition">
+      <enum>QSlider::TicksBelow</enum>
+     </property>
+     <property name="tickInterval">
+      <number>1</number>
+     </property>
+    </widget>
+   </item>
+   <item row="8" column="2">
+    <widget class="QLabel" name="label_9">
+     <property name="text">
+      <string>Completeness of systems modellings, including fuel handling, autopilot operation, startup &amp; failure procedures.</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="9" column="1" colspan="2">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>EditRatingsFilterDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>EditRatingsFilterDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/src/GUI/Launcher.ui b/src/GUI/Launcher.ui
new file mode 100644 (file)
index 0000000..46060e7
--- /dev/null
@@ -0,0 +1,678 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Launcher</class>
+ <widget class="QDialog" name="Launcher">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>696</width>
+    <height>711</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Start FlightGear</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0">
+   <property name="spacing">
+    <number>6</number>
+   </property>
+   <property name="leftMargin">
+    <number>6</number>
+   </property>
+   <property name="topMargin">
+    <number>6</number>
+   </property>
+   <property name="rightMargin">
+    <number>6</number>
+   </property>
+   <property name="bottomMargin">
+    <number>6</number>
+   </property>
+   <item>
+    <layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0,1">
+     <item row="3" column="2">
+      <spacer name="verticalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item row="0" column="2">
+      <widget class="QLabel" name="aircraftDescription">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="2">
+      <widget class="QLabel" name="airportDescription">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0" rowspan="4">
+      <widget class="QLabel" name="thumbnail">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>171</width>
+         <height>128</height>
+        </size>
+       </property>
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLabel" name="label_3">
+       <property name="text">
+        <string>Location:</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLabel" name="label_4">
+       <property name="text">
+        <string>Aircraft:</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLabel" name="label_5">
+       <property name="text">
+        <string>Settings:</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="2">
+      <widget class="QLabel" name="settingsDescription">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="tab">
+      <attribute name="title">
+       <string>Aircraft</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0,0">
+       <property name="spacing">
+        <number>4</number>
+       </property>
+       <property name="leftMargin">
+        <number>4</number>
+       </property>
+       <property name="topMargin">
+        <number>8</number>
+       </property>
+       <property name="rightMargin">
+        <number>4</number>
+       </property>
+       <property name="bottomMargin">
+        <number>4</number>
+       </property>
+       <item>
+        <widget class="QListView" name="aircraftList"/>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <property name="spacing">
+          <number>4</number>
+         </property>
+         <property name="topMargin">
+          <number>4</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>Search:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="aircraftFilter">
+           <property name="placeholderText">
+            <string>Search aircraft</string>
+           </property>
+           <property name="clearButtonEnabled">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="aircraftHistory">
+           <property name="autoDefault">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_5">
+         <item>
+          <widget class="QCheckBox" name="ratingsFilterCheck">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>Hide aircraft based on completeness (rating)</string>
+           </property>
+           <property name="checked">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="editRatingFilter">
+           <property name="text">
+            <string>Edit...</string>
+           </property>
+           <property name="autoDefault">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_2">
+      <attribute name="title">
+       <string>Location</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_4">
+       <property name="leftMargin">
+        <number>4</number>
+       </property>
+       <property name="rightMargin">
+        <number>4</number>
+       </property>
+       <property name="bottomMargin">
+        <number>4</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_6">
+         <item>
+          <widget class="QLabel" name="airportIdent">
+           <property name="text">
+            <string>Search:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="airportEdit">
+           <property name="placeholderText">
+            <string>Enter an ICAO code or search by name</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="airportHistory">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="autoDefault">
+            <bool>false</bool>
+           </property>
+           <property name="flat">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QStackedWidget" name="locationStack">
+         <property name="currentIndex">
+          <number>0</number>
+         </property>
+         <widget class="QWidget" name="diagramPage">
+          <layout class="QGridLayout" name="gridLayout">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <item row="0" column="0" colspan="2">
+            <widget class="AirportDiagram" name="airportDiagram" native="true">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QRadioButton" name="runwayRadio">
+             <property name="text">
+              <string>Runway:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QComboBox" name="runwayCombo"/>
+           </item>
+           <item row="2" column="0" rowspan="2">
+            <widget class="QRadioButton" name="parkingRadio">
+             <property name="text">
+              <string>Parking:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QCheckBox" name="onFinalCheckbox">
+             <property name="text">
+              <string>On 10-mile final</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QComboBox" name="parkingCombo"/>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="searchResultsPage">
+          <layout class="QVBoxLayout" name="verticalLayout_5">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <item>
+            <widget class="QListView" name="searchList"/>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="searchStatusPage">
+          <layout class="QVBoxLayout" name="verticalLayout_6">
+           <item>
+            <widget class="QLabel" name="searchIcon">
+             <property name="text">
+              <string>TextLabel</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignBottom|Qt::AlignHCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="searchStatusText">
+             <property name="text">
+              <string/>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignHCenter|Qt::AlignTop</set>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_3">
+      <attribute name="title">
+       <string>Settings</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <item>
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>Time of day:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QComboBox" name="timeOfDayCombo">
+           <item>
+            <property name="text">
+             <string>Current time</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Dawn</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Morning</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Noon</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Afternoon</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Dusk</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Evening</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Night</string>
+            </property>
+           </item>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="msaaCheckbox">
+         <property name="text">
+          <string>Enable Multi-sample anti-aliasing</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="rembrandtCheckbox">
+         <property name="text">
+          <string>Enable deferred rendering (Rembrandt)</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="terrasyncCheck">
+         <property name="text">
+          <string>Enable automatic scenery downloading (TerraSync)</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_7">
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeType">
+            <enum>QSizePolicy::Fixed</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QLabel" name="label_6">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If scenery download is disabled, you may need to download additional files from &lt;a href=&quot;http://www.flightgear.org/download/scenery/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;this page&lt;/span&gt;&lt;/a&gt; and install them in a scenery location; otherwise some objects may be missing from the world.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+           <property name="wordWrap">
+            <bool>true</bool>
+           </property>
+           <property name="openExternalLinks">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="fetchRealWxrCheckbox">
+         <property name="text">
+          <string>Fetch real weather online</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="fullScreenCheckbox">
+         <property name="text">
+          <string>Start full-screen</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="startPausedCheck">
+         <property name="text">
+          <string>Start paused</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
+         <item>
+          <widget class="QLabel" name="customAircraftDirLabel">
+           <property name="text">
+            <string>Custom aircraft directory:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="openAircraftDirButton">
+           <property name="text">
+            <string>Open in Finder</string>
+           </property>
+           <property name="autoDefault">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="groupBox">
+         <property name="title">
+          <string>Additional scenery locations</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_3">
+          <property name="leftMargin">
+           <number>8</number>
+          </property>
+          <property name="topMargin">
+           <number>8</number>
+          </property>
+          <property name="rightMargin">
+           <number>8</number>
+          </property>
+          <property name="bottomMargin">
+           <number>8</number>
+          </property>
+          <property name="spacing">
+           <number>0</number>
+          </property>
+          <item row="0" column="0" colspan="3">
+           <widget class="QListWidget" name="sceneryPathsList"/>
+          </item>
+          <item row="1" column="0">
+           <spacer name="horizontalSpacer_3">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>567</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+          <item row="1" column="2">
+           <widget class="QToolButton" name="removeSceneryPath">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>20</width>
+              <height>20</height>
+             </size>
+            </property>
+            <property name="text">
+             <string>-</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QToolButton" name="addSceneryPath">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>20</width>
+              <height>20</height>
+             </size>
+            </property>
+            <property name="text">
+             <string>+</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="quitButton">
+       <property name="text">
+        <string>Quit</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>412</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="runButton">
+       <property name="text">
+        <string>Run</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+       <property name="flat">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>AirportDiagram</class>
+   <extends>QWidget</extends>
+   <header location="global">GUI/AirportDiagram.hxx</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/GUI/QtLauncher.cxx b/src/GUI/QtLauncher.cxx
new file mode 100644 (file)
index 0000000..e177b89
--- /dev/null
@@ -0,0 +1,1138 @@
+#include "QtLauncher.hxx"
+
+// Qt
+#include <QProgressDialog>
+#include <QCoreApplication>
+#include <QAbstractListModel>
+#include <QDir>
+#include <QFileInfo>
+#include <QPixmap>
+#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>
+
+// Simgear
+#include <simgear/timing/timestamp.hxx>
+#include <simgear/props/props_io.hxx>
+#include <simgear/structure/exception.hxx>
+#include <simgear/misc/sg_path.hxx>
+
+#include "ui_Launcher.h"
+#include "EditRatingsFilterDialog.hxx"
+
+#include <Main/globals.hxx>
+#include <Navaids/NavDataCache.hxx>
+#include <Airports/airport.hxx>
+#include <Airports/dynamics.hxx> // for parking
+#include <Main/options.hxx>
+
+using namespace flightgear;
+
+const int MAX_RECENT_AIRPORTS = 32;
+const int MAX_RECENT_AIRCRAFT = 20;
+
+namespace { // anonymous namespace
+
+const int AircraftPathRole = Qt::UserRole + 1;
+const int AircraftAuthorsRole = Qt::UserRole + 2;
+const int AircraftRatingRole = Qt::UserRole + 100;
+
+void initNavCache()
+{
+    NavDataCache* cache = NavDataCache::instance();
+    if (cache->isRebuildRequired()) {
+        QProgressDialog rebuildProgress("Initialising navigation data, this may take several minutes",
+                                       QString() /* cancel text */,
+                                       0, 0);
+        rebuildProgress.setWindowModality(Qt::WindowModal);
+        rebuildProgress.show();
+
+        while (!cache->rebuild()) {
+            // sleep to give the rebuild thread more time
+            SGTimeStamp::sleepForMSec(50);
+            rebuildProgress.setValue(0);
+            QCoreApplication::processEvents();
+        }
+    }
+}
+
+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;
+        description = sim->getStringValue("description");
+        authors =  sim->getStringValue("author");
+
+        if (sim->hasChild("rating")) {
+            parseRatings(sim->getNode("rating"));
+        }
+
+        if (dir.exists("thumbnail.jpg")) {
+            thumbnail.load(dir.filePath("thumbnail.jpg"));
+            // resize to the standard size
+            if (thumbnail.height() > 128) {
+                thumbnail = thumbnail.scaledToHeight(128);
+            }
+        }
+
+    }
+
+    QString path;
+    QPixmap thumbnail;
+    QString description;
+    QString authors;
+    int ratings[4];
+
+private:
+    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");
+    }
+};
+
+class AircraftScanThread : public QThread
+{
+    Q_OBJECT
+public:
+    AircraftScanThread(QStringList dirsToScan) :
+        m_dirs(dirsToScan),
+        m_done(false)
+    {
+
+    }
+
+    /** 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()
+    {
+        Q_FOREACH(QString d, m_dirs) {
+            scanAircraftDir(QDir(d));
+            if (m_done) {
+                return;
+            }
+        }
+    }
+
+private:
+    void scanAircraftDir(QDir path)
+    {
+        QStringList filters;
+        filters << "*-set.xml";
+        Q_FOREACH(QFileInfo child, path.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
+            QDir childDir(child.absoluteFilePath());
+            Q_FOREACH(QFileInfo xmlChild, childDir.entryInfoList(filters, QDir::Files)) {
+                try {
+                    AircraftItem item(childDir, xmlChild.absoluteFilePath());
+                    // lock mutex whil we modify the items array
+                    {
+                        QMutexLocker g(&m_lock);
+                        m_items.append(item);
+                    }
+                } catch (sg_exception& e) {
+                    continue;
+                }
+
+                if (m_done) {
+                    return;
+                }
+            } // of set.xml iteration
+
+            emit addedItems();
+        } // of subdir iteration
+    }
+
+    QMutex m_lock;
+    QStringList m_dirs;
+    QList<AircraftItem> m_items;
+    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
+    {
+        const AircraftItem& item(m_items.at(index.row()));
+        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) {
+            return item.ratings[role - AircraftRatingRole];
+        } else if (role == Qt::ToolTipRole) {
+            return item.path;
+        }
+
+        return QVariant();
+    }
+
+  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);
+        endInsertRows();
+    }
+
+    void onScanFinished()
+    {
+        delete m_scanThread;
+        m_scanThread = NULL;
+    }
+
+private:
+    AircraftScanThread* m_scanThread;
+    QList<AircraftItem> m_items;
+};
+
+class AircraftItemDelegate : public QStyledItemDelegate
+{
+public:
+    const int MARGIN = 4;
+
+    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());
+
+        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 actualBounds;
+        painter->drawText(contentRect, Qt::TextWordWrap, description, &actualBounds);
+
+        QString authors = index.data(AircraftAuthorsRole).toString();
+
+        f.setPointSize(12);
+        painter->setFont(f);
+
+        QRect authorsRect = contentRect;
+        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));
+    }
+
+private:
+    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);
+        }
+    }
+};
+
+} // of anonymous namespace
+
+class AirportSearchModel : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    AirportSearchModel() :
+        m_searchActive(false)
+    {
+    }
+
+    void setSearch(QString t)
+    {
+        beginResetModel();
+
+        m_airports.clear();
+        m_ids.clear();
+
+        std::string term(t.toUpper().toStdString());
+        // try ICAO lookup first
+        FGAirportRef ref = FGAirport::findByIdent(term);
+        if (ref) {
+            m_ids.push_back(ref->guid());
+            m_airports.push_back(ref);
+        } else {
+            m_search.reset(new NavDataCache::ThreadedAirportSearch(term));
+            QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
+            m_searchActive = true;
+        }
+
+        endResetModel();
+    }
+
+    bool isSearchActive() const
+    {
+        return m_searchActive;
+    }
+
+    virtual int rowCount(const QModelIndex&) const
+    {
+        // if empty, return 1 for special 'no matches'?
+        return m_ids.size();
+    }
+
+    virtual QVariant data(const QModelIndex& index, int role) const
+    {
+        if (!index.isValid())
+            return QVariant();
+        
+        FGAirportRef apt = m_airports[index.row()];
+        if (!apt.valid()) {
+            apt = FGPositioned::loadById<FGAirport>(m_ids[index.row()]);
+            m_airports[index.row()] = apt;
+        }
+
+        if (role == Qt::DisplayRole) {
+            QString name = QString::fromStdString(apt->name());
+            return QString("%1: %2").arg(QString::fromStdString(apt->ident())).arg(name);
+        }
+
+        if (role == Qt::EditRole) {
+            return QString::fromStdString(apt->ident());
+        }
+
+        if (role == Qt::UserRole) {
+            return m_ids[index.row()];
+        }
+
+        return QVariant();
+    }
+
+    QString firstIdent() const
+    {
+        if (m_ids.empty())
+            return QString();
+
+        if (!m_airports.front().valid()) {
+            m_airports[0] = FGPositioned::loadById<FGAirport>(m_ids.front());
+        }
+
+        return QString::fromStdString(m_airports.front()->ident());
+    }
+
+Q_SIGNALS:
+    void searchComplete();
+
+private slots:
+    void onSearchResultsPoll()
+    {
+        PositionedIDVec newIds = m_search->results();
+        
+        beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
+        for (int i=m_ids.size(); i < newIds.size(); ++i) {
+            m_ids.push_back(newIds[i]);
+            m_airports.push_back(FGAirportRef()); // null ref
+        }
+        endInsertRows();
+
+        if (m_search->isComplete()) {
+            m_searchActive = false;
+            m_search.reset();
+            emit searchComplete();
+        } else {
+            QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
+        }
+    }
+
+private:
+    PositionedIDVec m_ids;
+    mutable std::vector<FGAirportRef> m_airports;
+    bool m_searchActive;
+    QScopedPointer<NavDataCache::ThreadedAirportSearch> m_search;
+};
+
+class AircraftProxyModel : public QSortFilterProxyModel
+{
+    Q_OBJECT
+public:
+    AircraftProxyModel(QObject* pr) :
+        QSortFilterProxyModel(pr),
+        m_ratingsFilter(true)
+    {
+        for (int i=0; i<4; ++i) {
+            m_ratings[i] = 3;
+        }
+    }
+
+    void setRatings(int* ratings)
+    {
+        ::memcpy(m_ratings, ratings, sizeof(int) * 4);
+        invalidate();
+    }
+
+public slots:
+    void setRatingFilterEnabled(bool e)
+    {
+        if (e == m_ratingsFilter) {
+            return;
+        }
+
+        m_ratingsFilter = e;
+        invalidate();
+    }
+
+protected:
+    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+    {
+        if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
+            return false;
+        }
+
+        if (m_ratingsFilter) {
+            QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+            for (int i=0; i<4; ++i) {
+                if (m_ratings[i] > index.data(AircraftRatingRole + i).toInt()) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+private:
+    bool m_ratingsFilter;
+    int m_ratings[4];
+};
+
+QtLauncher::QtLauncher() :
+    QDialog(),
+    m_ui(NULL)
+{
+    m_ui.reset(new Ui::Launcher);
+    m_ui->setupUi(this);
+
+    for (int i=0; i<4; ++i) {
+        m_ratingFilters[i] = 3;
+    }
+
+    m_airportsModel = new AirportSearchModel;
+    m_ui->searchList->setModel(m_airportsModel);
+    connect(m_ui->searchList, &QListView::clicked,
+            this, &QtLauncher::onAirportChoiceSelected);
+    connect(m_airportsModel, &AirportSearchModel::searchComplete,
+            this, &QtLauncher::onAirportSearchComplete);
+
+    SGPath p = SGPath::documents();
+    p.append("FlightGear");
+    p.append("Aircraft");
+    m_customAircraftDir = QString::fromStdString(p.str());
+    m_ui->customAircraftDirLabel->setText(QString("Custom aircraft folder: %1").arg(m_customAircraftDir));
+
+    globals->append_aircraft_path(m_customAircraftDir.toStdString());
+
+    // create and configure the proxy model
+    m_aircraftProxy = new AircraftProxyModel(this);
+    m_aircraftProxy->setSourceModel(new AircraftItemModel(this));
+
+    m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
+    m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
+    m_aircraftProxy->setSortRole(Qt::DisplayRole);
+    m_aircraftProxy->setDynamicSortFilter(true);
+
+    m_ui->aircraftList->setModel(m_aircraftProxy);
+    m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    m_ui->aircraftList->setItemDelegate(new AircraftItemDelegate);
+    m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
+    connect(m_ui->aircraftList, &QListView::clicked,
+            this, &QtLauncher::onAircraftSelected);
+
+    connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)),
+            this, SLOT(updateAirportDescription()));
+    connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)),
+            this, SLOT(updateAirportDescription()));
+    connect(m_ui->runwayRadio, SIGNAL(toggled(bool)),
+            this, SLOT(updateAirportDescription()));
+    connect(m_ui->parkingRadio, SIGNAL(toggled(bool)),
+            this, SLOT(updateAirportDescription()));
+    connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)),
+            this, SLOT(updateAirportDescription()));
+
+
+    connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
+    connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
+    connect(m_ui->airportEdit, SIGNAL(returnPressed()),
+            this, SLOT(onSearchAirports()));
+
+    connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
+            m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
+
+    connect(m_ui->airportHistory, &QPushButton::clicked,
+            this, &QtLauncher::onPopupAirportHistory);
+    connect(m_ui->aircraftHistory, &QPushButton::clicked,
+          this, &QtLauncher::onPopupAircraftHistory);
+
+    restoreSettings();
+
+    connect(m_ui->openAircraftDirButton, &QPushButton::clicked,
+          this, &QtLauncher::onOpenCustomAircraftDir);
+
+    QAction* qa = new QAction(this);
+    qa->setShortcut(QKeySequence("Ctrl+Q"));
+    connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
+    addAction(qa);
+
+    connect(m_ui->editRatingFilter, &QPushButton::clicked,
+            this, &QtLauncher::onEditRatingsFilter);
+    connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
+            m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
+
+    QIcon historyIcon(":/history-icon");
+    m_ui->aircraftHistory->setIcon(historyIcon);
+    m_ui->airportHistory->setIcon(historyIcon);
+
+    m_ui->searchIcon->setPixmap(QPixmap(":/search-icon"));
+
+    connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
+            this, SLOT(updateSettingsSummary()));
+    connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
+            this, SLOT(updateSettingsSummary()));
+    connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
+            this, SLOT(updateSettingsSummary()));
+    connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
+            this, SLOT(updateSettingsSummary()));
+    connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
+            this, SLOT(updateSettingsSummary()));
+
+    updateSettingsSummary();
+
+    connect(m_ui->addSceneryPath, &QToolButton::clicked,
+            this, &QtLauncher::onAddSceneryPath);
+    connect(m_ui->removeSceneryPath, &QToolButton::clicked,
+            this, &QtLauncher::onRemoveSceneryPath);
+}
+
+QtLauncher::~QtLauncher()
+{
+    
+}
+
+bool QtLauncher::runLauncherDialog()
+{
+     Q_INIT_RESOURCE(resources);
+
+    // startup the nav-cache now. This pre-empts normal startup of
+    // the cache, but no harm done. (Providing scenery paths are consistent)
+
+    initNavCache();
+
+  // setup scenery paths now, especially TerraSync path for airport
+  // parking locations (after they're downloaded)
+
+    QtLauncher dlg;
+    dlg.exec();
+    if (dlg.result() != QDialog::Accepted) {
+        return false;
+    }
+
+    return true;
+}
+
+void QtLauncher::restoreSettings()
+{
+    QSettings settings;
+    m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
+    m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
+    m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
+    m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
+    m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
+    m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
+    m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
+
+    // full paths to -set.xml files
+    m_recentAircraft = settings.value("recent-aircraft").toStringList();
+
+    if (!m_recentAircraft.empty()) {
+        m_selectedAircraft = m_recentAircraft.front();
+    } else {
+        // select the default C172p
+    }
+
+    updateSelectedAircraft();
+
+    // ICAO identifiers
+    m_recentAirports = settings.value("recent-airports").toStringList();
+    if (!m_recentAirports.empty()) {
+        setAirport(FGAirport::findByIdent(m_recentAirports.front().toStdString()));
+    }
+    updateAirportDescription();
+
+    // rating filters
+    m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
+    int index = 0;
+    Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
+        m_ratingFilters[index++] = v.toInt();
+    }
+
+    m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
+    m_aircraftProxy->setRatings(m_ratingFilters);
+
+    QStringList sceneryPaths = settings.value("scenery-paths").toStringList();
+    m_ui->sceneryPathsList->addItems(sceneryPaths);
+}
+
+void QtLauncher::saveSettings()
+{
+    QSettings settings;
+    settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
+    settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
+    settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
+    settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
+    settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
+    settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
+    settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
+    settings.setValue("recent-aircraft", m_recentAircraft);
+    settings.setValue("recent-airports", m_recentAirports);
+    settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
+
+    QStringList paths;
+    for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
+        paths.append(m_ui->sceneryPathsList->item(i)->text());
+    }
+
+    settings.setValue("scenery-paths", paths);
+}
+
+void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
+{
+    flightgear::Options* opt = flightgear::Options::sharedInstance();
+    std::string stdName(name.toStdString());
+    if (cbox->isChecked()) {
+        opt->addOption("enable-" + stdName, "");
+    } else {
+        opt->addOption("disable-" + stdName, "");
+    }
+}
+
+void QtLauncher::onRun()
+{
+    accept();
+
+    flightgear::Options* opt = flightgear::Options::sharedInstance();
+    setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
+    setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
+    setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
+    setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
+    setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
+
+    // aircraft
+    if (!m_selectedAircraft.isEmpty()) {
+        QFileInfo setFileInfo(m_selectedAircraft);
+        opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
+        QString setFile = setFileInfo.fileName();
+        Q_ASSERT(setFile.endsWith("-set.xml"));
+        setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
+        opt->addOption("aircraft", setFile.toStdString());
+
+      // manage aircraft history
+        if (m_recentAircraft.contains(m_selectedAircraft))
+          m_recentAircraft.removeOne(m_selectedAircraft);
+        m_recentAircraft.prepend(m_selectedAircraft);
+        if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
+          m_recentAircraft.pop_back();
+
+        qDebug() << Q_FUNC_INFO << "recent aircraft is now" << m_recentAircraft;
+    }
+
+    // airport / location
+    if (m_selectedAirport) {
+        opt->addOption("airport", m_selectedAirport->ident());
+    }
+
+    if (m_ui->runwayRadio->isChecked()) {
+        int index = m_ui->runwayCombo->currentData().toInt();
+        if (index >= 0) {
+            // explicit runway choice
+            opt->addOption("runway", m_selectedAirport->getRunwayByIndex(index)->ident());
+        }
+
+        if (m_ui->onFinalCheckbox->isChecked()) {
+            opt->addOption("glideslope", "3.0");
+            opt->addOption("offset-distance", "10.0"); // in nautical miles
+        }
+    } else if (m_ui->parkingRadio->isChecked()) {
+        // parking selection
+
+    }
+
+    // time of day
+    if (m_ui->timeOfDayCombo->currentIndex() != 0) {
+        QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
+        opt->addOption("timeofday", dayval.toStdString());
+    }
+
+    // scenery paths
+    for (int i=0; i<m_ui->sceneryPathsList->count(); ++i) {
+        QString path = m_ui->sceneryPathsList->item(i)->text();
+        opt->addOption("fg-scenery", path.toStdString());
+    }
+
+    saveSettings();
+}
+
+void QtLauncher::onQuit()
+{
+    reject();
+}
+
+void QtLauncher::onSearchAirports()
+{
+    QString search = m_ui->airportEdit->text();
+    m_airportsModel->setSearch(search);
+
+    if (m_airportsModel->isSearchActive()) {
+        m_ui->searchStatusText->setText(QString("Searching for '%1'").arg(search));
+        m_ui->locationStack->setCurrentIndex(2);
+    } else if (m_airportsModel->rowCount(QModelIndex()) == 1) {
+        QString ident = m_airportsModel->firstIdent();
+        setAirport(FGAirport::findByIdent(ident.toStdString()));
+        m_ui->locationStack->setCurrentIndex(0);
+    }
+}
+
+void QtLauncher::onAirportSearchComplete()
+{
+    int numResults = m_airportsModel->rowCount(QModelIndex());
+    if (numResults == 0) {
+        m_ui->searchStatusText->setText(QString("No matching airports for '%1'").arg(m_ui->airportEdit->text()));
+    } else if (numResults == 1) {
+        QString ident = m_airportsModel->firstIdent();
+        setAirport(FGAirport::findByIdent(ident.toStdString()));
+        m_ui->locationStack->setCurrentIndex(0);
+    } else {
+        m_ui->locationStack->setCurrentIndex(1);
+    }
+}
+
+void QtLauncher::onAirportChanged()
+{
+    m_ui->runwayCombo->setEnabled(m_selectedAirport);
+    m_ui->parkingCombo->setEnabled(m_selectedAirport);
+    m_ui->airportDiagram->setAirport(m_selectedAirport);
+
+    m_ui->runwayRadio->setChecked(true); // default back to runway mode
+    // unelss multiplayer is enabled ?
+
+    if (!m_selectedAirport) {
+        m_ui->airportDescription->setText(QString());
+        m_ui->airportDiagram->setEnabled(false);
+        return;
+    }
+
+    m_ui->airportDiagram->setEnabled(true);
+
+    m_ui->runwayCombo->clear();
+    m_ui->runwayCombo->addItem("Automatic", -1);
+    for (unsigned int r=0; r<m_selectedAirport->numRunways(); ++r) {
+        FGRunwayRef rwy = m_selectedAirport->getRunwayByIndex(r);
+        // add runway with index as data role
+        m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r);
+
+        m_ui->airportDiagram->addRunway(rwy);
+    }
+
+    m_ui->parkingCombo->clear();
+    FGAirportDynamics* dynamics = m_selectedAirport->getDynamics();
+    PositionedIDVec parkings = NavDataCache::instance()->airportItemsOfType(
+                                                                            m_selectedAirport->guid(),
+                                                                            FGPositioned::PARKING);
+    if (parkings.empty()) {
+        m_ui->parkingCombo->setEnabled(false);
+        m_ui->parkingRadio->setEnabled(false);
+    } else {
+        m_ui->parkingCombo->setEnabled(true);
+        m_ui->parkingRadio->setEnabled(true);
+        Q_FOREACH(PositionedID parking, parkings) {
+            FGParking* park = dynamics->getParking(parking);
+            m_ui->parkingCombo->addItem(QString::fromStdString(park->getName()), parking);
+
+            m_ui->airportDiagram->addParking(park);
+        }
+    }
+}
+
+void QtLauncher::updateAirportDescription()
+{
+    if (!m_selectedAirport) {
+        m_ui->airportDescription->setText(QString("No airport selected"));
+        return;
+    }
+
+    QString ident = QString::fromStdString(m_selectedAirport->ident()),
+        name = QString::fromStdString(m_selectedAirport->name());
+    QString locationOnAirport;
+    if (m_ui->runwayRadio->isChecked()) {
+        bool onFinal = m_ui->onFinalCheckbox->isChecked();
+        QString runwayName = (m_ui->runwayCombo->currentIndex() == 0) ?
+            "active runway" :
+            QString("runway %1").arg(m_ui->runwayCombo->currentText());
+
+        if (onFinal) {
+            locationOnAirport = QString("on 10-mile final to %1").arg(runwayName);
+        } else {
+            locationOnAirport = QString("on %1").arg(runwayName);
+        }
+    } else if (m_ui->parkingRadio->isChecked()) {
+        locationOnAirport =  QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
+    }
+
+    m_ui->airportDescription->setText(QString("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport));
+}
+
+void QtLauncher::onAirportChoiceSelected(const QModelIndex& index)
+{
+    m_ui->locationStack->setCurrentIndex(0);
+    setAirport(FGPositioned::loadById<FGAirport>(index.data(Qt::UserRole).toULongLong()));
+}
+
+void QtLauncher::onAircraftSelected(const QModelIndex& index)
+{
+    m_selectedAircraft = index.data(AircraftPathRole).toString();
+    updateSelectedAircraft();
+}
+
+void QtLauncher::updateSelectedAircraft()
+{
+    try {
+        QFileInfo info(m_selectedAircraft);
+        AircraftItem item(info.dir(), m_selectedAircraft);
+        m_ui->thumbnail->setPixmap(item.thumbnail);
+        m_ui->aircraftDescription->setText(item.description);
+    } catch (sg_exception& e) {
+        m_ui->thumbnail->setPixmap(QPixmap());
+        m_ui->aircraftDescription->setText("");
+    }
+}
+
+void QtLauncher::onPopupAirportHistory()
+{
+    if (m_recentAirports.isEmpty()) {
+        return;
+    }
+
+    QMenu m;
+    Q_FOREACH(QString aptCode, m_recentAirports) {
+        FGAirportRef apt = FGAirport::findByIdent(aptCode.toStdString());
+        QString name = QString::fromStdString(apt->name());
+        QAction* act = m.addAction(QString("%1 - %2").arg(aptCode).arg(name));
+        act->setData(aptCode);
+    }
+
+    QPoint popupPos = m_ui->airportHistory->mapToGlobal(m_ui->airportHistory->rect().bottomLeft());
+    QAction* triggered = m.exec(popupPos);
+    if (triggered) {
+        FGAirportRef apt = FGAirport::findByIdent(triggered->data().toString().toStdString());
+        setAirport(apt);
+        m_ui->airportEdit->clear();
+        m_ui->locationStack->setCurrentIndex(0);
+    }
+}
+
+QModelIndex QtLauncher::proxyIndexForAircraftPath(QString path) const
+{
+  return m_aircraftProxy->mapFromSource(sourceIndexForAircraftPath(path));
+}
+
+QModelIndex QtLauncher::sourceIndexForAircraftPath(QString path) const
+{
+    AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
+    Q_ASSERT(sourceModel);
+    return sourceModel->indexOfAircraftPath(path);
+}
+
+void QtLauncher::onPopupAircraftHistory()
+{
+    if (m_recentAircraft.isEmpty()) {
+        return;
+    }
+
+    QMenu m;
+    Q_FOREACH(QString path, m_recentAircraft) {
+        QModelIndex index = sourceIndexForAircraftPath(path);
+        if (!index.isValid()) {
+            // not scanned yet
+            continue;
+        }
+        QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
+        act->setData(path);
+    }
+
+    QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
+    QAction* triggered = m.exec(popupPos);
+    if (triggered) {
+        m_selectedAircraft = triggered->data().toString();
+        QModelIndex index = proxyIndexForAircraftPath(m_selectedAircraft);
+        m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
+                                                              QItemSelectionModel::ClearAndSelect);
+        m_ui->aircraftFilter->clear();
+        updateSelectedAircraft();
+    }
+}
+
+void QtLauncher::setAirport(FGAirportRef ref)
+{
+    if (m_selectedAirport == ref)
+        return;
+
+    m_selectedAirport = ref;
+    onAirportChanged();
+
+    if (ref.valid()) {
+        // maintain the recent airport list
+        QString icao = QString::fromStdString(ref->ident());
+        if (m_recentAirports.contains(icao)) {
+            // move to front
+            m_recentAirports.removeOne(icao);
+            m_recentAirports.push_front(icao);
+        } else {
+            // insert and trim list if necessary
+            m_recentAirports.push_front(icao);
+            if (m_recentAirports.size() > MAX_RECENT_AIRPORTS) {
+                m_recentAirports.pop_back();
+            }
+        }
+    }
+
+    updateAirportDescription();
+}
+
+void QtLauncher::onOpenCustomAircraftDir()
+{
+  QUrl u = QUrl::fromLocalFile(m_customAircraftDir);
+  QDesktopServices::openUrl(u);
+}
+
+void QtLauncher::onEditRatingsFilter()
+{
+    EditRatingsFilterDialog dialog(this);
+    dialog.setRatings(m_ratingFilters);
+
+    dialog.exec();
+    if (dialog.result() == QDialog::Accepted) {
+        QVariantList vl;
+        for (int i=0; i<4; ++i) {
+            m_ratingFilters[i] = dialog.getRating(i);
+            vl.append(m_ratingFilters[i]);
+        }
+        m_aircraftProxy->setRatings(m_ratingFilters);
+
+        QSettings settings;
+        settings.setValue("min-ratings", vl);
+    }
+}
+
+void QtLauncher::updateSettingsSummary()
+{
+    QStringList summary;
+    if (m_ui->timeOfDayCombo->currentIndex() > 0) {
+        summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
+    }
+
+    if (m_ui->rembrandtCheckbox->isChecked()) {
+        summary.append("Rembrandt enabled");
+    }
+
+    if (m_ui->fetchRealWxrCheckbox->isChecked()) {
+        summary.append("live weather");
+    }
+
+    if (m_ui->terrasyncCheck->isChecked()) {
+        summary.append("automatic scenery downloads");
+    }
+
+    if (m_ui->startPausedCheck->isChecked()) {
+        summary.append("paused");
+    }
+
+    QString s = summary.join(", ");
+    s[0] = s[0].toUpper();
+    m_ui->settingsDescription->setText(s);
+}
+
+void QtLauncher::onAddSceneryPath()
+{
+    QString path = QFileDialog::getExistingDirectory(this, tr("Choose scenery folder"));
+    if (!path.isEmpty()) {
+        m_ui->sceneryPathsList->addItem(path);
+        saveSettings();
+    }
+}
+
+void QtLauncher::onRemoveSceneryPath()
+{
+    if (m_ui->sceneryPathsList->currentItem()) {
+        delete m_ui->sceneryPathsList->currentItem();
+        saveSettings();
+    }
+}
+
+#include "QtLauncher.moc"
+
diff --git a/src/GUI/QtLauncher.hxx b/src/GUI/QtLauncher.hxx
new file mode 100644 (file)
index 0000000..62b630a
--- /dev/null
@@ -0,0 +1,77 @@
+
+#include <QDialog>
+#include <QScopedPointer>
+#include <QStringList>
+#include <QModelIndex>
+
+#include <Airports/airport.hxx>
+
+namespace Ui
+{
+    class Launcher;
+}
+
+class AirportSearchModel;
+class QModelIndex;
+class AircraftProxyModel;
+class QCheckBox;
+
+class QtLauncher : public QDialog
+{
+    Q_OBJECT
+public:
+    QtLauncher();
+    virtual ~QtLauncher();
+    
+    static bool runLauncherDialog();
+
+private slots:
+    void onRun();
+    void onQuit();
+
+    void onSearchAirports();
+
+    void onAirportChanged();
+
+    void onAirportChoiceSelected(const QModelIndex& index);
+    void onAircraftSelected(const QModelIndex& index);
+
+    void onPopupAirportHistory();
+    void onPopupAircraftHistory();
+
+    void onOpenCustomAircraftDir();
+
+    void onEditRatingsFilter();
+
+    void updateAirportDescription();
+    void updateSettingsSummary();
+
+    void onAirportSearchComplete();
+
+    void onAddSceneryPath();
+    void onRemoveSceneryPath();
+private:
+    void setAirport(FGAirportRef ref);
+    void updateSelectedAircraft();
+
+    void restoreSettings();
+    void saveSettings();
+    
+    QModelIndex proxyIndexForAircraftPath(QString path) const;
+    QModelIndex sourceIndexForAircraftPath(QString path) const;
+
+    void setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const;
+
+    QScopedPointer<Ui::Launcher> m_ui;
+    AirportSearchModel* m_airportsModel;
+    AircraftProxyModel* m_aircraftProxy;
+
+    FGAirportRef m_selectedAirport;
+
+    QString m_selectedAircraft;
+    QStringList m_recentAircraft,
+        m_recentAirports;
+    QString m_customAircraftDir;
+
+    int m_ratingFilters[4];
+};
diff --git a/src/GUI/history-icon.png b/src/GUI/history-icon.png
new file mode 100644 (file)
index 0000000..ddae8d3
Binary files /dev/null and b/src/GUI/history-icon.png differ
diff --git a/src/GUI/large-search-icon.png b/src/GUI/large-search-icon.png
new file mode 100644 (file)
index 0000000..8ab5253
Binary files /dev/null and b/src/GUI/large-search-icon.png differ
diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc
new file mode 100644 (file)
index 0000000..dc348f8
--- /dev/null
@@ -0,0 +1,6 @@
+<RCC>
+    <qresource prefix="/">
+        <file alias="history-icon">history-icon.png</file>
+        <file alias="search-icon">large-search-icon.png</file>
+    </qresource>
+</RCC>
\ No newline at end of file
index 0f151646477d538dc41c3d6a53374b5d6952ae0a..20a2ca777de8253bc3e0c73165483d529495256c 100644 (file)
@@ -46,3 +46,5 @@
 #cmakedefine HAVE_CRASHRPT
 
 #cmakedefine ENABLE_FLITE
+
+#cmakedefine HAVE_QT
index f799ae6081db62e784bda94f4f571720b76776f4..949b379ddad705acdf3e6d348719bcb7a6e99fdc 100644 (file)
@@ -152,6 +152,10 @@ if(ENABLE_FLITE)
     endif()
 endif()
 
+if (Qt5Core_FOUND)
+    target_link_libraries(fgfs Qt5::Widgets fglauncher)
+endif()
+
 if (APPLE)  
     install(TARGETS fgfs BUNDLE DESTINATION .)
 else()
index 1e544277b7d2e30f666d2fe7a723eb79e40bf35f..6d16c8c94d7def56fc81fdd2b308b9301d97c82b 100644 (file)
@@ -501,25 +501,37 @@ static void initAircraftDirsNasalSecurity()
     }
 }
 
-int fgInitAircraft(bool reinit)
+void fgInitAircraftPaths(bool reinit)
 {
-    if (!reinit) {
-        // FIXME - use Documents/FlightGear/Aircraft
-        SGPath userAircraftDir = globals->get_fg_home();
-        userAircraftDir.append("Aircraft");
-
-        SGSharedPtr<Root> pkgRoot(new Root(userAircraftDir, FLIGHTGEAR_VERSION));
-        // set the http client later (too early in startup right now)
-        globals->setPackageRoot(pkgRoot);
+  if (!reinit) {
+    SGPath userAircraftDir = SGPath::documents(globals->get_fg_home());
+    if (userAircraftDir != globals->get_fg_home()) {
+      userAircraftDir.append("FlightGear");
     }
+    userAircraftDir.append("Aircraft");
 
-    SGSharedPtr<Root> pkgRoot(globals->packageRoot());
-    SGPropertyNode* aircraftProp = fgGetNode("/sim/aircraft", true);
-    aircraftProp->setAttribute(SGPropertyNode::PRESERVE, true);
+    SGSharedPtr<Root> pkgRoot(new Root(userAircraftDir, FLIGHTGEAR_VERSION));
+    // set the http client later (too early in startup right now)
+    globals->setPackageRoot(pkgRoot);
+  }
+
+  SGSharedPtr<Root> pkgRoot(globals->packageRoot());
+  SGPropertyNode* aircraftProp = fgGetNode("/sim/aircraft", true);
+  aircraftProp->setAttribute(SGPropertyNode::PRESERVE, true);
+
+  if (!reinit) {
+      flightgear::Options::sharedInstance()->initPaths();
+  }
+}
 
+int fgInitAircraft(bool reinit)
+{
     if (!reinit) {
         flightgear::Options::sharedInstance()->initAircraft();
     }
+    
+    SGSharedPtr<Root> pkgRoot(globals->packageRoot());
+    SGPropertyNode* aircraftProp = fgGetNode("/sim/aircraft", true);
 
     string aircraftId(aircraftProp->getStringValue());
     PackageRef acftPackage = pkgRoot->getPackageById(aircraftId);
@@ -588,7 +600,12 @@ fgInitNav ()
       return false;
     }
   }
-  
+
+    // depend on when the NavCache was initialised, scenery paths may not
+    // have been setup. This is a safe place to consistently check the value,
+    // and drop the ground-nets if something has changed
+    cache->dropGroundnetsIfRequired();
+
   FGTACANList *channellist = new FGTACANList;
   globals->set_channellist( channellist );
   
@@ -1056,6 +1073,7 @@ void fgStartNewReset()
     }
 
     fgGetNode("/sim")->removeChild("aircraft-dir");
+    fgInitAircraftPaths(true);
     fgInitAircraft(true);
     
     render = new FGRenderer;
index 927ab9fdc2296cf33dadb3cf6ec0cf797c501ae1..ef8a6bd829af12db3a8faed08bc9ea7d8f12ca35 100644 (file)
@@ -39,6 +39,8 @@ bool fgInitHome();
 // Read in configuration (file and command line)
 int fgInitConfig ( int argc, char **argv, bool reinit );
 
+void fgInitAircraftPaths(bool reinit);
+
 int fgInitAircraft(bool reinit);
 
 // log various settings / configuration state
index 42dcf6ba4b3a22b0fd2cad1372df8b8b6719d2e7..581bc7aa38a61a6b766fe56a2b13a3890d353fcf 100644 (file)
@@ -80,6 +80,10 @@ extern bool global_crashRptEnabled;
 #include "subsystemFactory.hxx"
 #include "options.hxx"
 
+#if defined(HAVE_QT)
+#include <QApplication>
+#include <GUI/QtLauncher.hxx>
+#endif
 
 using namespace flightgear;
 
@@ -394,7 +398,14 @@ static void logToFile()
 }
 
 // Main top level initialization
-int fgMainInit( int argc, char **argv ) {
+int fgMainInit( int argc, char **argv )
+{
+#if defined(HAVE_QT)
+    QApplication app(argc, argv);
+    app.setOrganizationName("FlightGear");
+    app.setApplicationName("FlightGear");
+    app.setOrganizationDomain("flightgear.org");
+#endif
 
     // set default log levels
     sglog().setLogLevels( SG_ALL, SG_ALERT );
@@ -421,11 +432,6 @@ int fgMainInit( int argc, char **argv ) {
        SG_LOG( SG_GENERAL, SG_INFO, "Jenkins number/ID " << HUDSON_BUILD_NUMBER << ":"
                        << HUDSON_BUILD_ID);
 
-    // Allocate global data structures.  This needs to happen before
-    // we parse command line options
-
-    
-    
     // seed the random number generator
     sg_srandom_time();
 
@@ -447,7 +453,23 @@ int fgMainInit( int argc, char **argv ) {
     } else if (configResult == flightgear::FG_OPTIONS_EXIT) {
         return EXIT_SUCCESS;
     }
-    
+
+  // launcher needs to know the aircraft paths in use
+    fgInitAircraftPaths(false);
+
+#if defined(HAVE_QT)
+    bool showLauncher = flightgear::Options::checkForArg(argc, argv, "launcher");
+    // an Info.plist bundle can't define command line arguments, but it can set
+    // environment variables. This avoids needed a wrapper shell-script on OS-X.
+    showLauncher |= (::getenv("FG_LAUNCHER") != 0);
+
+    if (showLauncher) {
+        if (!QtLauncher::runLauncherDialog()) {
+            return EXIT_SUCCESS;
+        }
+    }
+#endif
+
     configResult = fgInitAircraft(false);
     if (configResult == flightgear::FG_OPTIONS_ERROR) {
         return EXIT_FAILURE;
index 7ba86d8271300227891ac0896bcdc6d75a66b5a4..65419fb2174245ebc933aeb8d0900ad885318102 100644 (file)
@@ -1484,6 +1484,7 @@ struct OptionDesc {
 
     {"language",                     true,  OPTION_IGNORE, "", false, "", 0 },
        {"console",                      false, OPTION_IGNORE,   "", false, "", 0 },
+    {"launcher",                     false, OPTION_IGNORE,   "", false, "", 0 },
     {"disable-rembrandt",            false, OPTION_BOOL,   "/sim/rendering/rembrandt/enabled", false, "", 0 },
     {"enable-rembrandt",             false, OPTION_BOOL,   "/sim/rendering/rembrandt/enabled", true, "", 0 },
     {"renderer",                     true,  OPTION_STRING, "/sim/rendering/rembrandt/renderer", false, "", 0 },
@@ -1953,18 +1954,22 @@ void Options::init(int argc, char **argv, const SGPath& appDataPath)
   config.append( "system.fgfsrc" );
   readConfig(config);
 }
-  
-void Options::initAircraft()
+
+void Options::initPaths()
 {
-  BOOST_FOREACH(const string& paths, valuesForOption("fg-aircraft")) {
-    globals->append_aircraft_paths(paths);
-  }
-  
-  const char* envp = ::getenv("FG_AIRCRAFT");
-  if (envp) {
-    globals->append_aircraft_paths(envp);
-  }
+    BOOST_FOREACH(const string& paths, valuesForOption("fg-aircraft")) {
+        globals->append_aircraft_paths(paths);
+    }
+
+    const char* envp = ::getenv("FG_AIRCRAFT");
+    if (envp) {
+        globals->append_aircraft_paths(envp);
+    }
 
+}
+
+void Options::initAircraft()
+{
   string aircraft;
   if (isOptionSet("aircraft")) {
     aircraft = valueForOption("aircraft");
index 97d71415ab10e603a33ebca45ec65f760e867c18..40005363f8cb904e0c0b950d5554977ff58ef21f 100644 (file)
@@ -98,7 +98,12 @@ public:
    * (set properties, etc). 
    */
   OptionResult processOptions();
-  
+
+    /**
+     * process command line options relating to scenery / aircraft / data paths
+     */
+    void initPaths();
+
   /**
    * init the aircraft options
    */
index f6756613bd6c19130d5c308b44c36282add9852f..18666d4d59563df742724ecc6ac25985c98b11f6 100644 (file)
@@ -1072,7 +1072,10 @@ NavDataCache::NavDataCache()
   }
     
   homePath.append(os.str());
-  
+
+    // permit additional DB connections from the same process
+    sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
+
   for (int t=0; t < MAX_TRIES; ++t) {
     try {
       d.reset(new NavDataCachePrivate(homePath, this));
@@ -1162,8 +1165,6 @@ bool NavDataCache::isRebuildRequired()
     return true;
   }
 
-  dropGroundnetsIfRequired();
-  
   SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: no main cache rebuild required");
   return false;
 }
@@ -2190,6 +2191,11 @@ bool NavDataCache::isReadOnly() const
     return d->readOnly;
 }
 
+SGPath NavDataCache::path() const
+{
+    return d->path;
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////
 // Transaction RAII object
     
@@ -2215,6 +2221,91 @@ void NavDataCache::Transaction::commit()
     _committed = true;
     _instance->commitTransaction();
 }
+
+/////////////////////////////////////////////////////////////////////////////
+
+class NavDataCache::ThreadedAirportSearch::ThreadedAirportSearchPrivate : public SGThread
+{
+public:
+    ThreadedAirportSearchPrivate() :
+        db(NULL),
+        isComplete(false),
+        quit(false)
+    {}
+
+    virtual void run()
+    {
+        while (!quit) {
+            int err = sqlite3_step(query);
+            if (err == SQLITE_DONE) {
+                break;
+            } else if (err == SQLITE_ROW) {
+                PositionedID r = sqlite3_column_int64(query, 0);
+                SGGuard<SGMutex> g(lock);
+                results.push_back(r);
+            } else if (err == SQLITE_BUSY) {
+                // sleep a tiny amount
+                SGTimeStamp::sleepForMSec(1);
+            } else {
+                std::string errMsg = sqlite3_errmsg(db);
+                SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " running threaded airport query");
+            }
+        }
+
+        SGGuard<SGMutex> g(lock);
+        isComplete = true;
+    }
+
+    SGMutex lock;
+    sqlite3* db;
+    sqlite3_stmt_ptr query;
+    PositionedIDVec results;
+    bool isComplete;
+    bool quit;
+};
+
+NavDataCache::ThreadedAirportSearch::ThreadedAirportSearch(const std::string& term) :
+    d(new ThreadedAirportSearchPrivate)
+{
+    SGPath p = NavDataCache::instance()->path();
+    int openFlags = SQLITE_OPEN_READONLY;
+    std::string pathUtf8 = simgear::strutils::convertWindowsLocal8BitToUtf8(p.str());
+    sqlite3_open_v2(pathUtf8.c_str(), &d->db, openFlags, NULL);
+
+    std::string sql = "SELECT rowid FROM positioned WHERE name LIKE '%" + term
+        + "%' AND type >= 1 AND type <= 3";
+    sqlite3_prepare_v2(d->db, sql.c_str(), sql.length(), &d->query, NULL);
+
+    d->start();
+}
+
+NavDataCache::ThreadedAirportSearch::~ThreadedAirportSearch()
+{
+    {
+        SGGuard<SGMutex> g(d->lock);
+        d->quit = true;
+    }
+
+    d->join();
+    sqlite3_finalize(d->query);
+    sqlite3_close_v2(d->db);
+}
+
+PositionedIDVec NavDataCache::ThreadedAirportSearch::results() const
+{
+    PositionedIDVec r;
+    {
+        SGGuard<SGMutex> g(d->lock);
+        r = d->results;
+    }
+    return r;
+}
+
+bool NavDataCache::ThreadedAirportSearch::isComplete() const
+{
+    SGGuard<SGMutex> g(d->lock);
+    return d->isComplete;
+}
     
 } // of namespace flightgear
 
index e5d9e7c06f3a01fc1d5ffedd78e2396f6e91abba..bf87c2423dd6c426065ceb5237e71f0873158c3d 100644 (file)
@@ -57,7 +57,9 @@ public:
     
 // singleton accessor
     static NavDataCache* instance();
-          
+
+    SGPath path() const;
+    
   /**
    * predicate - check if the cache needs to be rebuilt.
    * This can happen is the cache file is missing or damaged, or one of the
@@ -277,6 +279,20 @@ public:
     };
     
     bool isReadOnly() const;
+
+    class ThreadedAirportSearch
+    {
+    public:
+        ThreadedAirportSearch(const std::string& term);
+        ~ThreadedAirportSearch();
+        
+        PositionedIDVec results() const;
+
+        bool isComplete() const;
+    private:
+        class ThreadedAirportSearchPrivate;
+        std::auto_ptr<ThreadedAirportSearchPrivate> d;
+    };
 private:
   NavDataCache();
   
index 586cdbce083ed31607b6628715066ea4910a5049..901b72e14d8e0c23bf051a8f569e106c6ad848c3 100644 (file)
 
 #include <sstream>
 
+#if defined(HAVE_QT) && defined(SG_MAC)
+    #include <osgViewer/api/Cocoa/GraphicsWindowCocoa>
+#endif
+
 using namespace std;
 using namespace osg;
 
@@ -244,7 +248,14 @@ GraphicsWindow* WindowBuilder::getDefaultWindow()
     GraphicsContext::Traits* traits
         = new GraphicsContext::Traits(*defaultTraits);
     traits->windowName = "FlightGear";
-    
+
+#if defined(HAVE_QT) && defined(SG_MAC)
+    // avoid both QApplication and OSG::CocoaViewer doing single-application
+    // init (Apple menu, making front process, etc)
+    int flags = osgViewer::GraphicsWindowCocoa::WindowData::CheckForEvents;
+    traits->inheritedWindowData = new osgViewer::GraphicsWindowCocoa::WindowData(flags);
+#endif
+
     GraphicsContext* gc = GraphicsContext::createGraphicsContext(traits);
     if (gc) {
         defaultWindow = WindowSystemAdapter::getWSA()