# 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)
--- /dev/null
+#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
+}
--- /dev/null
+#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;
+};
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}")
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+<?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 & 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>
--- /dev/null
+<?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><html><head/><body><p>If scenery download is disabled, you may need to download additional files from <a href="http://www.flightgear.org/download/scenery/"><span style=" text-decoration: underline; color:#0000ff;">this page</span></a> and install them in a scenery location; otherwise some objects may be missing from the world.</p></body></html></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>
--- /dev/null
+#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"
+
--- /dev/null
+
+#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];
+};
--- /dev/null
+<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
#cmakedefine HAVE_CRASHRPT
#cmakedefine ENABLE_FLITE
+
+#cmakedefine HAVE_QT
endif()
endif()
+if (Qt5Core_FOUND)
+ target_link_libraries(fgfs Qt5::Widgets fglauncher)
+endif()
+
if (APPLE)
install(TARGETS fgfs BUNDLE DESTINATION .)
else()
}
}
-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);
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 );
}
fgGetNode("/sim")->removeChild("aircraft-dir");
+ fgInitAircraftPaths(true);
fgInitAircraft(true);
render = new FGRenderer;
// 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
#include "subsystemFactory.hxx"
#include "options.hxx"
+#if defined(HAVE_QT)
+#include <QApplication>
+#include <GUI/QtLauncher.hxx>
+#endif
using namespace flightgear;
}
// 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 );
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();
} 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;
{"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 },
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");
* (set properties, etc).
*/
OptionResult processOptions();
-
+
+ /**
+ * process command line options relating to scenery / aircraft / data paths
+ */
+ void initPaths();
+
/**
* init the aircraft options
*/
}
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));
return true;
}
- dropGroundnetsIfRequired();
-
SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: no main cache rebuild required");
return false;
}
return d->readOnly;
}
+SGPath NavDataCache::path() const
+{
+ return d->path;
+}
+
/////////////////////////////////////////////////////////////////////////////////////////
// Transaction RAII object
_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
// 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
};
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();
#include <sstream>
+#if defined(HAVE_QT) && defined(SG_MAC)
+ #include <osgViewer/api/Cocoa/GraphicsWindowCocoa>
+#endif
+
using namespace std;
using namespace osg;
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()