1
0
Fork 0

In-app launcher for Mac, based on Qt5.

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.
This commit is contained in:
James Turner 2014-12-26 15:20:51 +03:00
parent 0a28e95107
commit 78e8f53312
23 changed files with 2784 additions and 43 deletions

View 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)

264
src/GUI/AirportDiagram.cxx Normal file
View file

@ -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
}

View file

@ -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;
};

View 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}")

View file

@ -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;
}
}

View file

@ -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

View file

@ -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>

678
src/GUI/Launcher.ui Normal file
View file

@ -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>

1138
src/GUI/QtLauncher.cxx Normal file

File diff suppressed because it is too large Load diff

77
src/GUI/QtLauncher.hxx Normal file
View file

@ -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];
};

BIN
src/GUI/history-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

6
src/GUI/resources.qrc Normal file
View file

@ -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>

View file

@ -46,3 +46,5 @@
#cmakedefine HAVE_CRASHRPT
#cmakedefine ENABLE_FLITE
#cmakedefine HAVE_QT

View 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()

View file

@ -501,25 +501,37 @@ static void initAircraftDirsNasalSecurity()
}
}
void fgInitAircraftPaths(bool reinit)
{
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(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) {
// 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);
}
SGSharedPtr<Root> pkgRoot(globals->packageRoot());
SGPropertyNode* aircraftProp = fgGetNode("/sim/aircraft", true);
aircraftProp->setAttribute(SGPropertyNode::PRESERVE, true);
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;

View 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

View 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;

View 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::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);
}
}
void Options::initAircraft()
{
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);
}
string aircraft;
if (isOptionSet("aircraft")) {
aircraft = valueForOption("aircraft");

View 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
*/

View 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

View 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();

View file

@ -25,6 +25,10 @@
#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()