1
0
Fork 0

Better units handling in the launcher / UI

This commit is contained in:
James Turner 2018-07-17 11:55:25 +01:00
parent 9716274612
commit 9098219032
19 changed files with 1176 additions and 286 deletions

View file

@ -91,8 +91,7 @@ static double unitLengthAfterMapping(const QTransform& t)
}
AirportDiagram::AirportDiagram(QQuickItem* pr) :
BaseDiagram(pr),
m_approachDistanceNm(-1.0)
BaseDiagram(pr)
{
m_parkingIconPath.moveTo(0,0);
m_parkingIconPath.lineTo(-16, -16);
@ -187,21 +186,21 @@ void AirportDiagram::setSelection(QmlPositioned* pos)
update();
}
void AirportDiagram::setApproachExtensionNm(double distanceNm)
void AirportDiagram::setApproachExtension(QuantityValue distance)
{
if (m_approachDistanceNm == distanceNm) {
if (m_approachDistance == distance) {
return;
}
m_approachDistanceNm = distanceNm;
m_approachDistance = distance;
recomputeBounds(true);
update();
emit approachExtensionChanged();
}
double AirportDiagram::approachExtensionNm() const
QuantityValue AirportDiagram::approachExtension() const
{
return m_approachDistanceNm;
return m_approachDistance;
}
QmlPositioned* AirportDiagram::selection() const
@ -230,6 +229,16 @@ void AirportDiagram::setAirportGuid(qlonglong guid)
emit airportChanged();
}
void AirportDiagram::setApproachExtensionEnabled(bool e)
{
if (m_approachExtensionEnabled == e)
return;
m_approachExtensionEnabled = e;
recomputeBounds(true);
update();
emit approachExtensionChanged();
}
void AirportDiagram::addRunway(FGRunwayRef rwy)
{
Q_FOREACH(RunwayData rd, m_runways) {
@ -271,8 +280,8 @@ void AirportDiagram::doComputeBounds()
}
FGRunway* runwaySelection = fgpositioned_cast<FGRunway>(m_selection);
if (runwaySelection && (m_approachDistanceNm > 0.0)) {
double d = SG_NM_TO_METER * m_approachDistanceNm;
if (runwaySelection && m_approachExtensionEnabled) {
double d = m_approachDistance.convertToUnit(Units::Kilometers).value * 1000;
QPointF pt = project(runwaySelection->pointOnCenterline(-d));
extendBounds(pt);
}
@ -381,10 +390,10 @@ void AirportDiagram::paintContents(QPainter* p)
headingDeg = runwaySelection->headingDeg();
}
if (runwaySelection && (m_approachDistanceNm > 0.0)) {
if (runwaySelection && m_approachExtensionEnabled) {
p->setTransform(t);
// draw approach extension point
double d = SG_NM_TO_METER * m_approachDistanceNm;
double d = m_approachDistance.convertToUnit(Units::Kilometers).value * 1000;
QPointF pt = project(runwaySelection->pointOnCenterline(-d));
QPointF pt2 = project(runwaySelection->geod());
QPen pen(Qt::yellow);

View file

@ -25,6 +25,8 @@
#include <QPixmap>
#include "UnitsModel.hxx"
#include <Airports/parking.hxx>
#include <Airports/runways.hxx>
#include <simgear/math/sg_geodesy.hxx>
@ -39,7 +41,8 @@ class AirportDiagram : public BaseDiagram
Q_PROPERTY(QmlPositioned* selection READ selection WRITE setSelection NOTIFY selectionChanged)
Q_PROPERTY(qlonglong airport READ airportGuid WRITE setAirportGuid NOTIFY airportChanged)
Q_PROPERTY(double approachExtensionNm READ approachExtensionNm WRITE setApproachExtensionNm NOTIFY approachExtensionChanged)
Q_PROPERTY(bool approachExtensionEnabled READ approachExtensionEnabled WRITE setApproachExtensionEnabled NOTIFY approachExtensionChanged)
Q_PROPERTY(QuantityValue approachExtension READ approachExtension WRITE setApproachExtension NOTIFY approachExtensionChanged)
public:
AirportDiagram(QQuickItem* pr = nullptr);
virtual ~AirportDiagram();
@ -54,12 +57,18 @@ public:
void setSelection(QmlPositioned* pos);
void setApproachExtensionNm(double distanceNm);
double approachExtensionNm() const;
void setApproachExtension(QuantityValue distance);
QuantityValue approachExtension() const;
qlonglong airportGuid() const;
void setAirportGuid(qlonglong guid);
bool approachExtensionEnabled() const
{
return m_approachExtensionEnabled;
}
void setApproachExtensionEnabled(bool e);
Q_SIGNALS:
void clicked(QmlPositioned* pos);
@ -130,7 +139,8 @@ private:
QPainterPath m_parkingIconPath, // arrow points right
m_parkingIconLeftPath; // arrow points left
double m_approachDistanceNm;
QuantityValue m_approachDistance;
bool m_approachExtensionEnabled = false;
QPainterPath m_helipadIconPath;
FGPositionedRef m_selection;

View file

@ -152,6 +152,8 @@ if (HAVE_QT)
QmlNavCacheWrapper.cxx
QmlRadioButtonHelper.cxx
QmlRadioButtonHelper.hxx
UnitsModel.cxx
UnitsModel.hxx
)
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)

View file

@ -47,14 +47,17 @@
#include "AirportDiagram.hxx"
#include "NavaidDiagram.hxx"
#include "QmlRadioButtonHelper.hxx"
#include "UnitsModel.hxx"
using namespace simgear::pkg;
LauncherController::LauncherController(QObject *parent, QWindow* window) :
QObject(parent),
m_window(window)
{
qRegisterMetaType<QuantityValue>();
qRegisterMetaTypeStreamOperators<QuantityValue>("Quantity");
m_serversModel = new MPServersModel(this);
m_location = new LocationController(this);
m_locationHistory = new RecentLocationsModel(this);
@ -134,6 +137,9 @@ void LauncherController::initQML()
qmlRegisterUncreatableType<LaunchConfig>("FlightGear.Launcher", 1, 0, "LaunchConfig", "Singleton API");
qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API");
qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum");
qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");
qmlRegisterType<FileDialogWrapper>("FlightGear.Launcher", 1, 0, "FileDialog");
qmlRegisterType<QmlAircraftInfo>("FlightGear.Launcher", 1, 0, "AircraftInfo");
qmlRegisterType<PopupWindowTracker>("FlightGear.Launcher", 1, 0, "PopupWindowTracker");
@ -368,7 +374,8 @@ QString LauncherController::selectAircraftStateAutomatically()
return {};
if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("cruise")) {
if (m_location->altitudeFt() > 6000) {
const double altitudeFt = m_location->altitude().convertToUnit(Units::FeetMSL).value;
if (altitudeFt > 6000) {
return "cruise";
}
}

View file

@ -375,6 +375,12 @@ LocationController::LocationController(QObject *parent) :
m_detailQml = new QmlPositioned(this);
m_baseQml = new QmlPositioned(this);
m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000};
m_defaultAirspeed = QuantityValue{Units::Knots, 120};
m_defaultHeading = QuantityValue{Units::DegreesTrue, 0};
m_defaultOffsetDistance = QuantityValue{Units::NauticalMiles, 1.0};
m_defaultOffsetRadial = QuantityValue{Units::DegreesTrue, 90};
// chain location and offset updated to description
connect(this, &LocationController::baseLocationChanged,
this, &LocationController::descriptionChanged);
@ -392,10 +398,8 @@ void LocationController::setLaunchConfig(LaunchConfig *config)
{
m_config = config;
connect(m_config, &LaunchConfig::collect, this, &LocationController::onCollectConfig);
connect(m_config, &LaunchConfig::save, this, &LocationController::onSaveCurrentLocation);
connect(m_config, &LaunchConfig::restore, this, &LocationController::onRestoreCurrentLocation);
}
void LocationController::restoreSearchHistory()
@ -433,10 +437,10 @@ bool LocationController::isParkedLocation() const
bool LocationController::isAirborneLocation() const
{
const bool altIsPositive = (m_altitudeFt > 0);
const bool altIsPositive = (m_altitude.value > 0);
if (m_locationIsLatLon) {
return (m_altitudeType != AltitudeType::Off) && altIsPositive;
return m_altitudeEnabled && altIsPositive;
}
if (m_airportLocation) {
@ -454,10 +458,10 @@ bool LocationController::isAirborneLocation() const
}
// relative to a navaid or fix - base off altitude.
return (m_altitudeType != AltitudeType::Off) && altIsPositive;
return m_altitudeEnabled && altIsPositive;
}
int LocationController::offsetRadial() const
QuantityValue LocationController::offsetRadial() const
{
return m_offsetRadial;
}
@ -646,7 +650,7 @@ QmlPositioned *LocationController::baseLocation() const
return m_baseQml;
}
void LocationController::setOffsetRadial(int offsetRadial)
void LocationController::setOffsetRadial(QuantityValue offsetRadial)
{
if (m_offsetRadial == offsetRadial)
return;
@ -655,12 +659,12 @@ void LocationController::setOffsetRadial(int offsetRadial)
emit offsetChanged();
}
void LocationController::setOffsetNm(double offsetNm)
void LocationController::setOffsetDistance(QuantityValue d)
{
if (qFuzzyCompare(m_offsetNm, offsetNm))
if (m_offsetDistance == d)
return;
m_offsetNm = offsetNm;
m_offsetDistance = d;
emit offsetChanged();
}
@ -724,23 +728,17 @@ void LocationController::restoreLocation(QVariantMap l)
m_baseQml->setInner(m_location);
}
if (l.contains("altitude-type")) {
m_altitudeFt = l.value("altitude", 6000).toInt();
m_flightLevel = l.value("flight-level").toInt();
m_altitudeType = static_cast<AltitudeType>(l.value("altitude-type").toInt());
} else {
m_altitudeType = Off;
}
m_altitudeEnabled = l.contains("altitude");
m_speedEnabled = l.contains("speed");
m_headingEnabled = l.contains("heading");
m_airspeedKnots = l.value("speed", 120).toInt();
m_headingDeg = l.value("heading").toInt();
m_altitude = l.value("altitude", QVariant::fromValue(m_defaultAltitude)).value<QuantityValue>();
m_airspeed = l.value("speed", QVariant::fromValue(m_defaultAirspeed)).value<QuantityValue>();
m_heading = l.value("heading", QVariant::fromValue(m_defaultHeading)).value<QuantityValue>();
m_offsetEnabled = l.value("offset-enabled").toBool();
m_offsetRadial = l.value("offset-bearing").toInt();
m_offsetNm = l.value("offset-distance", 10).toInt();
m_offsetRadial = l.value("offset-bearing", QVariant::fromValue(m_defaultOffsetRadial)).value<QuantityValue>();
m_offsetDistance = l.value("offset-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
m_tuneNAV1 = l.value("tune-nav1-radio").toBool();
if (m_airportLocation) {
@ -766,7 +764,7 @@ void LocationController::restoreLocation(QVariantMap l)
}
m_onFinal = l.value("location-on-final").toBool();
m_offsetNm = l.value("location-apt-final-distance").toInt();
m_offsetDistance = l.value("location-apt-final-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
} // of location is an airport
} catch (const sg_exception&) {
qWarning() << "Errors restoring saved location, clearing";
@ -808,7 +806,7 @@ QVariantMap LocationController::saveLocation() const
if (m_airportLocation) {
locationSet.insert("location-on-final", m_onFinal);
locationSet.insert("location-apt-final-distance", m_offsetNm);
locationSet.insert("location-apt-final-distance", QVariant::fromValue(m_offsetDistance));
if (m_useActiveRunway) {
locationSet.insert("location-apt-runway", "ACTIVE");
} else if (m_detailLocation) {
@ -822,30 +820,22 @@ QVariantMap LocationController::saveLocation() const
} // of location is an airport
} // of m_location is valid
if (m_altitudeType != Off) {
locationSet.insert("altitude-type", m_altitudeType);
if ((m_altitudeType == MSL_Feet) || (m_altitudeType == AGL_Feet)) {
locationSet.insert("altitude", m_altitudeFt);
}
if (m_altitudeType == FlightLevel) {
locationSet.insert("flight-level", m_flightLevel);
}
if (m_altitudeEnabled) {
locationSet.insert("altitude", QVariant::fromValue(m_altitude));
}
if (m_speedEnabled) {
locationSet.insert("speed", m_airspeedKnots);
locationSet.insert("speed", QVariant::fromValue(m_airspeed));
}
if (m_headingEnabled) {
locationSet.insert("heading", m_headingDeg);
locationSet.insert("heading", QVariant::fromValue(m_heading));
}
if (m_offsetEnabled) {
locationSet.insert("offset-enabled", m_offsetEnabled);
locationSet.insert("offset-bearing", m_offsetRadial);
locationSet.insert("offset-distance",m_offsetNm);
locationSet.insert("offset-bearing", QVariant::fromValue(m_offsetRadial));
locationSet.insert("offset-distance", QVariant::fromValue(m_offsetDistance));
}
locationSet.insert("text", description());
@ -916,7 +906,7 @@ void LocationController::setLocationProperties()
if (m_onFinal) {
fgSetDouble("/sim/presets/glideslope-deg", 3.0);
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetNm);
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
fgSetBool("/sim/presets/on-ground", false);
}
} else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
@ -961,46 +951,65 @@ void LocationController::setLocationProperties()
applyPositionOffset();
applyAltitude();
applyAirspeed();
} // of navaid location
}
void LocationController::applyAirspeed()
{
if (m_speedEnabled && (m_airspeed.unit != Units::NoUnits)) {
if (m_airspeed.unit == Units::Knots) {
m_config->setArg("vc", QString::number(m_airspeed.value));
} else if (m_airspeed.unit == Units::Mach) {
m_config->setArg("mach", QString::number(m_airspeed.value));
} else {
qWarning() << Q_FUNC_INFO << "unsupported airpseed unit" << m_airspeed.unit;
}
}
}
void LocationController::applyPositionOffset()
{
if (m_speedEnabled) {
m_config->setArg("vc", QString::number(m_airspeedKnots));
}
if (m_headingEnabled) {
m_config->setArg("heading", QString::number(m_headingDeg));
if (m_headingEnabled && (m_heading.unit != Units::NoUnits)) {
if (m_heading.unit == Units::DegreesTrue) {
m_config->setArg("heading", QString::number(m_heading.value));
} else {
qWarning() << Q_FUNC_INFO << "unsupported heading unit" << m_heading.unit;
}
}
if (m_offsetEnabled) {
// flip direction of azimuth to balance the flip done in fgApplyStartOffset
// I don't know why that flip exists but changing it there will break
// command-line compatability so compensating here instead
int offsetAzimuth = m_offsetRadial - 180;
int offsetAzimuth = m_offsetRadial.value - 180;
m_config->setArg("offset-azimuth", QString::number(offsetAzimuth));
m_config->setArg("offset-distance", QString::number(m_offsetNm));
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm));
}
}
void LocationController::applyAltitude()
{
switch (m_altitudeType) {
case Off:
if (!m_altitudeEnabled)
return;
switch (m_altitude.unit) {
default:
qWarning() << Q_FUNC_INFO << "unsupported altitdue unit";
break;
case MSL_Feet:
m_config->setArg("altitude", QString::number(m_altitudeFt));
case Units::FeetMSL:
m_config->setArg("altitude", QString::number(m_altitude.value));
break;
case AGL_Feet:
case Units::FeetAGL:
// fixme - allow the sim to accpet AGL start position
m_config->setArg("altitude", QString::number(m_altitudeFt));
m_config->setArg("altitude", QString::number(m_altitude.value));
break;
case FlightLevel:
case Units::FlightLevel:
// FIXME - allow the sim to accept real FlightLevel arguments
m_config->setArg("altitude", QString::number(m_flightLevel * 100));
m_config->setArg("altitude", QString::number(m_altitude.value * 100));
break;
}
}
@ -1017,6 +1026,7 @@ void LocationController::onCollectConfig()
m_config->setArg("lon", QString::number(m_geodLocation.getLongitudeDeg(), 'f', 8));
applyPositionOffset();
applyAltitude();
applyAirspeed();
return;
}
@ -1044,13 +1054,11 @@ void LocationController::onCollectConfig()
if (m_onFinal) {
m_config->setArg("glideslope", std::string("3.0"));
m_config->setArg("offset-distance", QString::number(m_offsetNm));
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm));
m_config->setArg("on-ground", std::string("false"));
if (m_speedEnabled) {
m_config->setArg("vc", QString::number(m_airspeedKnots));
}
applyAirspeed();
applyAltitude();
}
} else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
@ -1089,6 +1097,7 @@ void LocationController::onCollectConfig()
m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid()));
applyPositionOffset();
applyAltitude();
applyAirspeed();
} // of navaid location
}
@ -1112,30 +1121,6 @@ void LocationController::setNavRadioOption()
}
}
void LocationController::onAirportRunwayClicked(FGRunwayRef rwy)
{
// if (rwy) {
// m_ui->runwayRadio->setChecked(true);
// int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(rwy->ident()));
// m_ui->runwayCombo->setCurrentIndex(rwyIndex);
// m_ui->airportDiagram->setSelectedRunway(rwy);
// }
// updateDescription();
}
void LocationController::onAirportParkingClicked(FGParkingRef park)
{
// if (park) {
// m_ui->parkingRadio->setChecked(true);
// int parkingIndex = m_ui->parkingCombo->findData(park->getIndex());
// m_ui->parkingCombo->setCurrentIndex(parkingIndex);
// m_ui->airportDiagram->setSelectedParking(park);
// }
// updateDescription();
}
QString compassPointFromHeading(int heading)
{
const int labelArc = 360 / 8;
@ -1173,6 +1158,7 @@ QString LocationController::description() const
name = QString::fromStdString(m_location->name());
name = fixNavaidName(name);
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
if (m_airportLocation) {
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
@ -1181,7 +1167,7 @@ QString LocationController::description() const
if (m_useActiveRunway) {
if (m_onFinal) {
locationOnAirport = tr("on %1-mile final to active runway").arg(m_offsetNm);
locationOnAirport = tr("on %1-mile final to active runway").arg(offsetNm);
} else {
locationOnAirport = tr("on active runway");
}
@ -1189,7 +1175,7 @@ QString LocationController::description() const
QString runwayName = QString("runway %1").arg(QString::fromStdString(m_detailLocation->ident()));
if (m_onFinal) {
locationOnAirport = tr("on %2-mile final to %1").arg(runwayName).arg(m_offsetNm);
locationOnAirport = tr("on %2-mile final to %1").arg(runwayName).arg(offsetNm);
} else {
locationOnAirport = tr("on %1").arg(runwayName);
}
@ -1202,8 +1188,8 @@ QString LocationController::description() const
QString offsetDesc = tr("at");
if (m_offsetEnabled) {
offsetDesc = tr("%1nm %2 of").
arg(m_offsetNm, 0, 'f', 1).
arg(compassPointFromHeading(m_offsetRadial));
arg(offsetNm, 0, 'f', 1).
arg(compassPointFromHeading(m_offsetRadial.value));
}
QString navaidType;

View file

@ -29,6 +29,7 @@
#include "QtLauncher_fwd.hxx"
#include "LaunchConfig.hxx"
#include "QmlPositioned.hxx"
#include "UnitsModel.hxx"
class NavSearchModel;
@ -44,20 +45,16 @@ class LocationController : public QObject
Q_PROPERTY(QList<QObject*> airportParkings READ airportParkings NOTIFY baseLocationChanged)
Q_PROPERTY(bool offsetEnabled READ offsetEnabled WRITE setOffsetEnabled NOTIFY offsetChanged)
Q_PROPERTY(int offsetRadial READ offsetRadial WRITE setOffsetRadial NOTIFY offsetChanged)
Q_PROPERTY(bool offsetBearingIsTrue MEMBER m_offsetBearingIsTrue NOTIFY offsetChanged)
Q_PROPERTY(double offsetNm READ offsetNm WRITE setOffsetNm NOTIFY offsetChanged)
Q_PROPERTY(QuantityValue offsetRadial READ offsetRadial WRITE setOffsetRadial NOTIFY offsetChanged)
Q_PROPERTY(QuantityValue offsetDistance READ offsetDistance WRITE setOffsetDistance NOTIFY offsetChanged)
Q_PROPERTY(bool headingEnabled MEMBER m_headingEnabled NOTIFY configChanged)
Q_PROPERTY(bool speedEnabled MEMBER m_speedEnabled NOTIFY configChanged)
Q_PROPERTY(bool altitudeEnabled MEMBER m_altitudeEnabled NOTIFY configChanged)
Q_PROPERTY(AltitudeType altitudeType MEMBER m_altitudeType NOTIFY configChanged)
Q_PROPERTY(int headingDeg MEMBER m_headingDeg NOTIFY configChanged)
Q_PROPERTY(int altitudeFt MEMBER m_altitudeFt NOTIFY configChanged)
Q_PROPERTY(int flightLevel MEMBER m_flightLevel NOTIFY configChanged)
Q_PROPERTY(int airspeedKnots MEMBER m_airspeedKnots NOTIFY configChanged)
Q_PROPERTY(QuantityValue heading MEMBER m_heading NOTIFY configChanged)
Q_PROPERTY(QuantityValue altitude MEMBER m_altitude NOTIFY configChanged)
Q_PROPERTY(QuantityValue airspeed MEMBER m_airspeed NOTIFY configChanged)
Q_PROPERTY(bool onFinal READ onFinal WRITE setOnFinal NOTIFY configChanged)
Q_PROPERTY(bool isAirportLocation READ isAirportLocation NOTIFY baseLocationChanged)
@ -78,16 +75,6 @@ public:
explicit LocationController(QObject *parent = nullptr);
~LocationController();
enum AltitudeType
{
Off = 0,
MSL_Feet,
AGL_Feet,
FlightLevel
};
Q_ENUMS(AltitudeType)
void setLaunchConfig(LaunchConfig* config);
QString description() const;
@ -109,11 +96,11 @@ public:
/// used to automatically select aircraft state
bool isAirborneLocation() const;
int offsetRadial() const;
QuantityValue offsetRadial() const;
double offsetNm() const
QuantityValue offsetDistance() const
{
return m_offsetNm;
return m_offsetDistance;
}
Q_INVOKABLE void setBaseLocation(QmlPositioned* pos);
@ -170,14 +157,14 @@ public:
return m_locationIsLatLon;
}
int altitudeFt() const
QuantityValue altitude() const
{
return m_altitudeFt;
return m_altitude;
}
public slots:
void setOffsetRadial(int offsetRadial);
void setOffsetRadial(QuantityValue offsetRadial);
void setOffsetNm(double offsetNm);
void setOffsetDistance(QuantityValue offsetNm);
void setOffsetEnabled(bool offsetEnabled);
@ -201,17 +188,12 @@ private Q_SLOTS:
private:
void onSearchComplete();
void onAirportRunwayClicked(FGRunwayRef rwy);
void onAirportParkingClicked(FGParkingRef park);
void addToRecent(FGPositionedRef pos);
void setNavRadioOption();
void applyPositionOffset();
void applyAltitude();
void applyAirspeed();
NavSearchModel* m_searchModel = nullptr;
@ -227,20 +209,22 @@ private:
QmlPositioned* m_baseQml = nullptr;
bool m_offsetEnabled = false;
int m_offsetRadial = 0;
double m_offsetNm = 0.0;
bool m_offsetBearingIsTrue = false;
int m_headingDeg = 0;
int m_altitudeFt= 0;
int m_airspeedKnots = 150;
QuantityValue m_offsetRadial;
QuantityValue m_offsetDistance;
QuantityValue m_heading;
QuantityValue m_altitude;
QuantityValue m_airspeed;
QuantityValue m_defaultAirspeed, m_defaultAltitude,
m_defaultHeading, m_defaultOffsetDistance, m_defaultOffsetRadial;
bool m_onFinal = false;
bool m_useActiveRunway = true;
bool m_tuneNAV1 = false;
bool m_useAvailableParking;
bool m_headingEnabled = false;
bool m_speedEnabled = false;
AltitudeType m_altitudeType = Off;
int m_flightLevel = 0;
bool m_altitudeEnabled = false;
bool m_skipFromArgs = false;
};

View file

@ -30,13 +30,8 @@
#include <Navaids/NavDataCache.hxx>
NavaidDiagram::NavaidDiagram(QQuickItem* pr) :
BaseDiagram(pr),
m_offsetEnabled(false),
m_offsetDistanceNm(5.0),
m_offsetBearingDeg(0),
m_headingDeg(0)
BaseDiagram(pr)
{
}
void NavaidDiagram::setNavaid(qlonglong nav)
@ -81,23 +76,26 @@ void NavaidDiagram::setOffsetEnabled(bool offset)
emit offsetChanged();
}
void NavaidDiagram::setOffsetDistanceNm(double distanceNm)
void NavaidDiagram::setOffsetDistance(QuantityValue distanceNm)
{
m_offsetDistanceNm = distanceNm;
if (distanceNm == m_offsetDistance)
return;
m_offsetDistance = distanceNm;
update();
emit offsetChanged();
}
void NavaidDiagram::setOffsetBearingDeg(int bearingDeg)
void NavaidDiagram::setOffsetBearing(QuantityValue bearing)
{
m_offsetBearingDeg = bearingDeg;
m_offsetBearing = bearing;
update();
emit offsetChanged();
}
void NavaidDiagram::setHeadingDeg(int headingDeg)
void NavaidDiagram::setHeading(QuantityValue headingDeg)
{
m_headingDeg = headingDeg;
m_heading = headingDeg;
update();
emit offsetChanged();
}
@ -108,8 +106,9 @@ void NavaidDiagram::paintContents(QPainter *painter)
SGGeod aircraftPos = m_geod;
if (m_offsetEnabled) {
double d = m_offsetDistanceNm * SG_NM_TO_METER;
SGGeod offsetGeod = SGGeodesy::direct(m_geod, m_offsetBearingDeg, d);
double d = m_offsetDistance.convertToUnit(Units::Kilometers).value * 1000;
SGGeod offsetGeod = SGGeodesy::direct(m_geod, m_offsetBearing.value, d);
QPointF offset = project(offsetGeod);
QPen pen(Qt::green);
@ -120,7 +119,7 @@ void NavaidDiagram::paintContents(QPainter *painter)
aircraftPos = offsetGeod;
}
paintAirplaneIcon(painter, aircraftPos, m_headingDeg);
paintAirplaneIcon(painter, aircraftPos, m_heading.value);
}
void NavaidDiagram::doComputeBounds()
@ -135,8 +134,8 @@ void NavaidDiagram::doComputeBounds()
}
if (m_offsetEnabled) {
double d = m_offsetDistanceNm * SG_NM_TO_METER;
SGGeod offsetPos = SGGeodesy::direct(m_geod, m_offsetBearingDeg, d);
double d = m_offsetDistance.convertToUnit(Units::Kilometers).value * 1000;
SGGeod offsetPos = SGGeodesy::direct(m_geod, m_offsetBearing.value, d);
extendBounds(project(offsetPos));
}
}

View file

@ -23,6 +23,7 @@
#include "BaseDiagram.hxx"
#include "QmlPositioned.hxx"
#include "UnitsModel.hxx"
#include <Navaids/navrecord.hxx>
#include <simgear/math/sg_geodesy.hxx>
@ -34,10 +35,10 @@ class NavaidDiagram : public BaseDiagram
Q_PROPERTY(qlonglong navaid READ navaid WRITE setNavaid NOTIFY locationChanged)
Q_PROPERTY(QmlGeod geod READ geod WRITE setGeod NOTIFY locationChanged)
Q_PROPERTY(int headingDeg READ headingDeg WRITE setHeadingDeg NOTIFY offsetChanged)
Q_PROPERTY(int offsetBearingDeg READ offsetBearingDeg WRITE setOffsetBearingDeg NOTIFY offsetChanged)
Q_PROPERTY(QuantityValue heading READ heading WRITE setHeading NOTIFY offsetChanged)
Q_PROPERTY(QuantityValue offsetBearing READ offsetBearing WRITE setOffsetBearing NOTIFY offsetChanged)
Q_PROPERTY(bool offsetEnabled READ isOffsetEnabled WRITE setOffsetEnabled NOTIFY offsetChanged)
Q_PROPERTY(double offsetDistanceNm READ offsetDistanceNm WRITE setOffsetDistanceNm NOTIFY offsetChanged)
Q_PROPERTY(QuantityValue offsetDistance READ offsetDistance WRITE setOffsetDistance NOTIFY offsetChanged)
public:
NavaidDiagram(QQuickItem* pr = nullptr);
@ -54,17 +55,17 @@ public:
void setOffsetEnabled(bool offset);
void setOffsetDistanceNm(double distanceNm);
double offsetDistanceNm() const
{ return m_offsetDistanceNm; }
void setOffsetDistance(QuantityValue distance);
QuantityValue offsetDistance() const
{ return m_offsetDistance; }
void setOffsetBearingDeg(int bearingDeg);
int offsetBearingDeg() const
{ return m_offsetBearingDeg; }
void setOffsetBearing(QuantityValue bearing);
QuantityValue offsetBearing() const
{ return m_offsetBearing; }
void setHeadingDeg(int headingDeg);
int headingDeg() const
{ return m_headingDeg; }
void setHeading(QuantityValue heading);
QuantityValue heading() const
{ return m_heading; }
signals:
void locationChanged();
@ -78,10 +79,10 @@ private:
FGPositionedRef m_navaid;
SGGeod m_geod;
bool m_offsetEnabled;
double m_offsetDistanceNm;
int m_offsetBearingDeg;
int m_headingDeg;
bool m_offsetEnabled = false;
QuantityValue m_offsetDistance;
QuantityValue m_offsetBearing;
QuantityValue m_heading;
};
#endif // of GUI_NAVAID_DIAGRAM_HXX

View file

@ -68,6 +68,7 @@
#include "LauncherMainWindow.hxx"
#include "LaunchConfig.hxx"
#include "UnitsModel.hxx"
using namespace flightgear;
using namespace simgear::pkg;
@ -310,6 +311,9 @@ void initQSettings()
static bool qSettingsInitDone = false;
if (!qSettingsInitDone) {
qRegisterMetaType<QuantityValue>();
qRegisterMetaTypeStreamOperators<QuantityValue>("QuantityValue");
qSettingsInitDone = true;
string fgHome = globals->get_fg_home().utf8Str();

411
src/GUI/UnitsModel.cxx Normal file
View file

@ -0,0 +1,411 @@
// UnitsModel.cxx - part of GUI launcher using Qt5
//
// Written by James Turner, started July 2018
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "UnitsModel.hxx"
#include <cmath>
#include <simgear/constants.h>
#include <QIntValidator>
#include <QDoubleValidator>
#include <QDataStream>
#include <QDebug>
namespace
{
struct UnitData
{
UnitData(QString sn, QString ln, QString metrics, bool pfx = false) :
shortName(sn), longName(ln),
maxTextForMetrics(metrics),
isPrefix(pfx) {}
UnitData(QString sn, QString ln,
QString metrics,
bool pfx,
double min, double max,
double step = 1.0,
bool wraps = false,
int dps = 0) :
shortName(sn), longName(ln),
maxTextForMetrics(metrics),
isPrefix(pfx),
valueWraps(wraps),
minValue(min), maxValue(max), stepSize(step),
decimals(dps)
{}
QString shortName;
QString longName;
QString maxTextForMetrics;
bool isPrefix = false;
bool valueWraps = false;
double minValue = 0.0;
double maxValue = 9999999.0;
double stepSize = 1.0;
int decimals = 0;
};
std::vector<UnitData> static_unitData = {
{ "", "", "" }, // noUnits
{ "ft", "feet above sea-level (MSL)", "000000", false, -2000, 180000, 50},
{ "ft AGL", "feet above ground level (AGL)", "000000", false, 0, 180000, 50},
{ "FL", "Flight-level", "000", true /* prefix */, 0.0, 500.0, 5.0},
{ "m", "meters above sea-level (MSL)", "000000", false, -500, 100000, 50},
{ "kts", "Knots", "9999", false, 0, 999999, 10.0},
{ "M", "Mach", "00.000", true /* prefix */, 0.0, 99.0, 0.05, false /* no wrap */, 3 /* decimal places */},
{ "°True", "degrees true", "000", false, 0, 359, 5.0, true /* wraps */},
{ "°Mag", "degrees magnetic", "000", false, 0, 359, 5.0, true /* wraps */},
{ "UTC", "Universal coordinated time", ""},
{ "Local", "Local time", ""},
{ "Nm", "Nautical miles", "00000", false, 0, 999999, 1.0, false /* no wrap */, 1 /* decimal places */},
{ "Km", "Kilometers", "00000", false, 0, 999999, 1.0, false /* no wrap */, 1 /* decimal places */}
};
// order here corresponds to the Mode enum
std::vector<UnitsModel::UnitVec> static_modeData = {
{ Units::FeetMSL, Units::FlightLevel},
{ Units::FeetMSL, Units::FeetAGL, Units::FlightLevel},
{ Units::FeetMSL, Units::MetersMSL, Units::FlightLevel},
{ Units::Knots, Units::Mach },
{ Units::Knots },
{ Units::DegreesMagnetic, Units::DegreesTrue },
{ Units::TimeLocal, Units::TimeUTC},
{ Units::NauticalMiles }
};
const int UnitLongNameRole = Qt::UserRole + 1;
const int UnitIsPrefixRole = Qt::UserRole + 2;
const int UnitMinValueRole = Qt::UserRole + 3;
const int UnitMaxValueRole = Qt::UserRole + 4;
const int UnitStepSizeRole = Qt::UserRole + 5;
const int UnitDecimalsRole = Qt::UserRole + 6;
const int UnitValueWrapsRole = Qt::UserRole + 7;
} // of anonymous namespace
UnitsModel::UnitsModel()
{
m_enabledUnits = static_modeData.at(m_mode);
}
int UnitsModel::rowCount(const QModelIndex &) const
{
return m_enabledUnits.size();
}
QVariant UnitsModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if ((row < 0) || (row >= m_enabledUnits.size()))
return {};
const Units::Type u = m_enabledUnits.at(row);
const UnitData& ud = static_unitData.at(u);
switch (role) {
case Qt::DisplayRole: return ud.shortName;
case UnitLongNameRole: return ud.longName;
case UnitIsPrefixRole: return ud.isPrefix;
case UnitMinValueRole: return ud.minValue;
case UnitMaxValueRole: return ud.maxValue;
case UnitStepSizeRole: return ud.stepSize;
case UnitValueWrapsRole: return ud.valueWraps;
case UnitDecimalsRole: return ud.decimals;
default:
break;
}
return {};
}
QValidator* UnitsModel::validator() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
if (ud.decimals > 0) {
return new QDoubleValidator(ud.minValue, ud.maxValue, ud.decimals);
}
if ((u == Units::TimeLocal) || (u == Units::TimeUTC)) {
return nullptr; // no validation
}
return new QIntValidator(static_cast<int>(ud.minValue),
static_cast<int>(ud.maxValue));
}
QString UnitsModel::maxTextForMetrics() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.maxTextForMetrics;
}
bool UnitsModel::isPrefix() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.isPrefix;
}
bool UnitsModel::doesWrap() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.valueWraps;
}
QString UnitsModel::shortText() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.shortName;
}
Units::Type UnitsModel::selectedUnit() const
{
return m_enabledUnits.at(m_activeIndex);
}
int UnitsModel::numChoices() const
{
return m_enabledUnits.size();
}
QHash<int, QByteArray> UnitsModel::roleNames() const
{
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
result[Qt::DisplayRole] = "shortName";
result[UnitLongNameRole] = "longName";
result[UnitIsPrefixRole] = "isPrefix";
result[UnitValueWrapsRole] = "valueDoesWrap";
result[UnitMinValueRole] = "minValue";
result[UnitMaxValueRole] = "maxValue";
result[UnitStepSizeRole] = "stepSize";
result[UnitDecimalsRole] = "decimalPlaces";
return result;
}
int UnitsModel::numDecimals() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.decimals;
}
double UnitsModel::minValue() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.minValue;
}
double UnitsModel::maxValue() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.maxValue;
}
double UnitsModel::stepSize() const
{
const auto u = m_enabledUnits.at(m_activeIndex);
const UnitData& ud = static_unitData.at(u);
return ud.stepSize;
}
void UnitsModel::setMode(Units::Mode mode)
{
if (m_mode == mode)
return;
m_mode = mode;
emit modeChanged(m_mode);
beginResetModel();
m_enabledUnits = static_modeData.at(mode);
endResetModel();
}
void UnitsModel::setSelectedIndex(int selectedIndex)
{
if (m_activeIndex == selectedIndex)
return;
if ((selectedIndex < 0) || (selectedIndex >= m_enabledUnits.size()))
return;
m_activeIndex = selectedIndex;
emit selectionChanged(m_activeIndex);
}
void UnitsModel::setSelectedUnit(int u)
{
auto it = std::find(m_enabledUnits.begin(), m_enabledUnits.end(), static_cast<Units::Type>(u));
if (it == m_enabledUnits.end()) {
qWarning() << Q_FUNC_INFO << "unit" << u << "not enabled for mode" << m_mode;
return;
}
int index = std::distance(m_enabledUnits.begin(), it);
if (index != m_activeIndex) {
m_activeIndex = index;
emit selectionChanged(m_activeIndex);
}
}
QuantityValue::QuantityValue()
{
}
QuantityValue::QuantityValue(Units::Type u, double v) :
value(v),
unit(u)
{
}
QuantityValue QuantityValue::convertToUnit(Units::Type u) const
{
// special case a no-change
if (unit == u)
return *this;
if (unit == Units::NoUnits) {
return {u, 0.0};
}
switch (u) {
case Units::NauticalMiles:
{
if (unit == Units::Kilometers) {
return {u, value * SG_METER_TO_NM * 1000};
}
break;
}
case Units::Kilometers:
{
if (unit == Units::NauticalMiles) {
return {u, value * SG_NM_TO_METER * 0.001};
}
break;
}
case Units::FeetMSL:
{
if (unit == Units::FlightLevel) {
return {u, value * 100};
} else if (unit == Units::MetersMSL) {
return {u, value * SG_METER_TO_FEET};
}
break;
}
case Units::Mach:
{
if (unit == Units::Knots) {
// obviously this depends on altitude, let's
// use the value at sea level for now
return {u, value / 667.0};
}
}
case Units::Knots:
{
if (unit == Units::Mach) {
// obviously this depends on altitude, let's
// use the value at sea level for now
return {u, value * 667.0};
}
}
case Units::DegreesMagnetic:
case Units::DegreesTrue:
{
// we don't have a location to apply mag-var, so just keep the
// current value
if ((unit == Units::DegreesMagnetic) || (unit == Units::DegreesTrue)) {
return {u, value};
}
}
case Units::FlightLevel:
{
if (unit == Units::FeetMSL) {
return {u, static_cast<double>(static_cast<int>(value / 100))};
}
if (unit == Units::MetersMSL) {
return {u, static_cast<double>(static_cast<int>(value * SG_METER_TO_FEET / 100))};
}
break;
}
default:
qWarning() << Q_FUNC_INFO << "unhandled case:" << u << "from" << unit;
break;
}
return {};
}
QuantityValue QuantityValue::convertToUnit(int u) const
{
return convertToUnit(static_cast<Units::Type>(u));
}
bool QuantityValue::operator==(const QuantityValue &v) const
{
if (v.unit != unit)
return false;
int dp = static_unitData.at(unit).decimals;
const auto aInt = static_cast<qlonglong>(value * pow(10, dp));
const auto bInt = static_cast<qlonglong>(v.value * pow(10, dp));
return aInt == bInt;
}
bool QuantityValue::operator!=(const QuantityValue &v) const
{
return !(*this == v);
}
QDataStream &operator<<(QDataStream &out, const QuantityValue &value)
{
out << static_cast<quint8>(value.unit);
if (value.unit != Units::NoUnits)
out << value.value;
return out;
}
QDataStream &operator>>(QDataStream &in, QuantityValue &value)
{
quint8 unit;
in >> unit;
value.unit = static_cast<Units::Type>(unit);
if (unit != Units::NoUnits)
in >> value.value;
return in;
}

177
src/GUI/UnitsModel.hxx Normal file
View file

@ -0,0 +1,177 @@
// UnitsModel.cxx - part of GUI launcher using Qt5
//
// Written by James Turner, started July 2018
//
// Copyright (C) 2018 James Turner <james@flightgear.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef UNITSMODEL_HXX
#define UNITSMODEL_HXX
#include <vector>
#include <QAbstractListModel>
class QValidator;
class QDataStream;
class Units : public QObject
{
Q_OBJECT
public:
/**
* @brief This enum stores units / types of values used in the
* simulator. They're not strictly all units, but they map to the
* same concept for the user: selecting the dimension of values they
* are inputting
*/
enum Type
{
NoUnits = 0,
FeetMSL,
FeetAGL,
FlightLevel,
MetersMSL,
Knots,
Mach,
DegreesTrue,
DegreesMagnetic,
TimeUTC,
TimeLocal,
NauticalMiles,
Kilometers
};
enum Mode
{
Altitude = 0, // MSL, FlightLevel
AltitudeIncludingAGL,
AltitudeIncludingMeters,
Speed, // Mach or knots
SpeedOnlyKnots = 4,
Heading, // degrees true or magnetic
Timezone,
Distance = 7 // Nm only for now
};
Q_ENUMS(Mode)
Q_ENUMS(Type)
};
class QuantityValue
{
Q_GADGET
Q_PROPERTY(double value MEMBER value)
Q_PROPERTY(Units::Type unit MEMBER unit)
public:
QuantityValue();
QuantityValue(Units::Type u, double v);
QuantityValue convertToUnit(Units::Type u) const;
Q_INVOKABLE QuantityValue convertToUnit(int u) const;
// precision aware comparisom
bool operator==(const QuantityValue& v) const;
bool operator!=(const QuantityValue& v) const;
double value = 0.0;
Units::Type unit = Units::NoUnits;
};
QDataStream &operator<<(QDataStream &out, const QuantityValue &value);
QDataStream &operator>>(QDataStream &in, QuantityValue &value);
Q_DECLARE_METATYPE(QuantityValue)
class UnitsModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(Units::Mode mode READ mode WRITE setMode NOTIFY modeChanged)
Q_PROPERTY(int numChoices READ numChoices NOTIFY modeChanged)
Q_PROPERTY(int selectedIndex READ selectedIndex WRITE setSelectedIndex NOTIFY selectionChanged)
Q_PROPERTY(int selectedUnit READ selectedUnit WRITE setSelectedUnit NOTIFY selectionChanged)
Q_PROPERTY(double minValue READ minValue NOTIFY selectionChanged)
Q_PROPERTY(double maxValue READ maxValue NOTIFY selectionChanged)
Q_PROPERTY(double stepSize READ stepSize NOTIFY selectionChanged)
Q_PROPERTY(int numDecimals READ numDecimals NOTIFY selectionChanged)
Q_PROPERTY(QString maxTextForMetrics READ maxTextForMetrics NOTIFY selectionChanged)
Q_PROPERTY(QString shortText READ shortText NOTIFY selectionChanged)
Q_PROPERTY(bool isPrefix READ isPrefix NOTIFY selectionChanged)
Q_PROPERTY(bool wraps READ doesWrap NOTIFY selectionChanged)
Q_PROPERTY(QValidator* validator READ validator NOTIFY selectionChanged)
public:
UnitsModel();
using UnitVec = std::vector<Units::Type>;
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Units::Mode mode() const
{
return m_mode;
}
int selectedIndex() const
{
return m_activeIndex;
}
double minValue() const;
double maxValue() const;
double stepSize() const;
int numDecimals() const;
QValidator *validator() const;
QString maxTextForMetrics() const;
bool isPrefix() const;
bool doesWrap() const;
QString shortText() const;
Units::Type selectedUnit() const;
int numChoices() const;
public slots:
void setMode(Units::Mode mode);
void setSelectedIndex(int selectedIndex);
void setSelectedUnit(int u);
signals:
void modeChanged(Units::Mode mode);
void selectionChanged(int selectedIndex);
private:
Units::Mode m_mode = Units::Altitude;
int m_activeIndex = 0;
UnitVec m_enabledUnits;
};
#endif // UNITSMODEL_HXX

View file

@ -6,15 +6,17 @@ Text {
signal clicked();
property bool clickable: true
property bool enabled: true
property color baseTextColor: Style.baseTextColor
color: mouse.containsMouse ? Style.themeColor : baseTextColor
color: enabled ? (mouse.containsMouse ? Style.themeColor : baseTextColor) : Style.disabledTextColor
font.pixelSize: Style.baseFontPixelSize
font.underline: mouse.containsMouse
MouseArea {
id: mouse
enabled: root.clickable
hoverEnabled: root.clickable
enabled: root.clickable && root.enabled
hoverEnabled: root.clickable && root.enabled
anchors.fill: parent
onClicked: parent.clicked();

View file

@ -5,7 +5,7 @@ FocusScope {
id: root
property string label
property bool enabled: true
property int value: 0
property double value: 0.0
property alias min: validator.bottom
property int max: validator.top
property alias decimals: validator.decimals
@ -88,7 +88,7 @@ FocusScope {
interval: 800
onTriggered: {
if (edit.activeFocus) {
commit(parseInt(edit.text));
commit(parseFloat(edit.text));
}
}
}

View file

@ -29,7 +29,8 @@ Item {
syncUIFromController();
}
approachExtensionNm: _location.onFinal ? _location.offsetNm : -1.0
approachExtensionEnabled: _location.onFinal
approachExtension: _location.offsetDistance
}
// not very declarative, try to remove this over time
@ -183,16 +184,12 @@ Item {
Row {
height: offsetNmEdit.height
DoubleSpinbox {
NumericalEdit {
id: offsetNmEdit
value: _location.offsetNm
onCommit: _location.offsetNm = newValue;
quantity: _location.offsetDistance
onCommit: _location.offsetDistance = newValue;
label: qsTr("At")
suffix: "Nm"
min: 0.0
max: 40.0
decimals: 1
maxDigits: 5
unitsMode: Units.Distance
live: true
anchors.verticalCenter: parent.verticalCenter
enabled: onFinalBox.enableOnFinal
@ -216,17 +213,13 @@ Item {
anchors.verticalCenter: parent.verticalCenter
}
IntegerSpinbox {
NumericalEdit {
id: airspeedSpinbox
label: qsTr("Airspeed:")
suffix: "kts"
min: 0
max: 10000 // more for spaceships?
step: 5
maxDigits: 5
unitsMode: Units.SpeedOnlyKnots
enabled: _location.speedEnabled && onFinalBox.enableOnFinal
value: _location.airspeedKnots
onCommit: _location.airspeedKnots = newValue
quantity: _location.airspeed
onCommit: _location.airspeed = newValue
anchors.verticalCenter: parent.verticalCenter
}
}

View file

@ -1,6 +1,7 @@
import QtQuick 2.4
import "."
import FlightGear.Launcher 1.0
import FlightGear 1.0
Row {
id: root
@ -10,57 +11,21 @@ Row {
ToggleSwitch {
id: altitudeToggle
checked: _location.altitudeType !== LocationController.Off
checked: _location.altitudeEnabled
function toggle(newChecked) {
_location.altitudeType = (newChecked ? LocationController.MSL_Feet
: LocationController.Off)
_location.altitudeEnabled = newChecked
}
enabled: parent.enabled
}
readonly property bool __rowEnabled: root.enabled && altitudeToggle.checked
readonly property bool __rowEnabled: root.enabled && _location.altitudeEnabled
IntegerSpinbox {
NumericalEdit {
label: qsTr("Altitude:")
suffix: "ft"
min: -1000 // Dead Sea, Schiphol
max: 200000
step: 100
maxDigits: 6
enabled: __rowEnabled
visible: !altitudeTypeChoice.isFlightLevel
value: _location.altitudeFt
onCommit: _location.altitudeFt = newValue
}
IntegerSpinbox {
label: qsTr("Altitude:")
prefix: "FL"
min: 0
max: 1000
step: 10
maxDigits: 3
enabled: __rowEnabled
visible: altitudeTypeChoice.isFlightLevel
value: _location.flightLevel
onCommit: _location.flightLevel = newValue
}
PopupChoice {
id: altitudeTypeChoice
enabled: __rowEnabled && (_location.altitudeType !== LocationController.Off)
currentIndex: Math.max(0, _location.altitudeType - 1)
readonly property bool isFlightLevel: (currentIndex == 2)
model: [qsTr("Above mean sea-level (MSL)"),
qsTr("Above ground (AGL)"),
qsTr("Flight-level")]
function select(index)
{
_location.altitudeType = index + 1;
}
quantity: _location.altitude
onCommit: _location.altitude = newValue
}
}

View file

@ -22,9 +22,9 @@ Item {
anchors.fill: parent
offsetEnabled: _location.offsetEnabled
offsetBearingDeg: _location.offsetRadial
offsetDistanceNm: _location.offsetNm
headingDeg: _location.headingDeg
offsetBearing: _location.offsetRadial
offsetDistance: _location.offsetDistance
heading: _location.heading
}
Rectangle {
@ -81,16 +81,12 @@ Item {
onCheckedChanged: _location.speedEnabled = checked;
}
IntegerSpinbox {
NumericalEdit {
label: qsTr("Airspeed:")
suffix: "kts"
min: 0
max: 10000 // more for spaceships?
step: 5
maxDigits: 5
enabled: _location.speedEnabled
value: _location.airspeedKnots
onCommit: _location.airspeedKnots = newValue
quantity: _location.airspeed
onCommit: _location.airspeed = newValue
unitsMode: Units.Speed
}
Item {
@ -107,16 +103,12 @@ Item {
}
}
IntegerSpinbox {
NumericalEdit {
label: qsTr("Heading:")
suffix: "deg" // FIXME use Unicode degree symbol
min: 0
max: 359
live: true
maxDigits: 3
unitsMode: Units.Heading
enabled: _location.headingEnabled
value: _location.headingDeg
onCommit: _location.headingDeg = newValue
quantity: _location.heading
onCommit: _location.heading = newValue
}
}
@ -137,37 +129,23 @@ Item {
}
}
DoubleSpinbox {
NumericalEdit {
id: offsetNmEdit
value: _location.offsetNm
onCommit: _location.offsetNm = newValue
min: 0.0
max: 40.0
suffix: "Nm"
maxDigits: 5
decimals: 1
quantity: _location.offsetDistance
onCommit: _location.offsetDistance = newValue
live: true
anchors.verticalCenter: parent.verticalCenter
enabled: offsetToggle.checked
unitsMode: Units.Distance
}
StyledText {
text: qsTr(" on bearing ")
enabled: _location.offsetEnabled
anchors.verticalCenter: parent.verticalCenter
}
IntegerSpinbox {
id: offsetBearingEdit
suffix: "deg" // FIXME use Unicode degree symbol
min: 0
max: 359
maxDigits: 3
live: true
anchors.verticalCenter: parent.verticalCenter
NumericalEdit {
label: qsTr(" on bearing ")
unitsMode: Units.Heading
enabled: offsetToggle.checked
value: _location.offsetRadial
quantity: _location.offsetRadial
onCommit: _location.offsetRadial = newValue
live: true
}
}
} // main layout column

View file

@ -0,0 +1,359 @@
import QtQuick 2.4
import FlightGear 1.0
import QtQuick.Window 2.0
import FlightGear.Launcher 1.0
import "."
FocusScope {
id: root
property string label
property bool enabled: true
property var quantity
property bool live: false
property alias unitsMode: units.mode
UnitsModel {
id: units
}
implicitHeight: editFrame.height
// we have a margin between the frame and the label, and on each
implicitWidth: label.width + editFrame.width + Style.margin
signal commit(var newValue);
function doCommit(newValue)
{
var q = quantity;
q.value = newValue;
commit(q);
}
function parseTextAsValue()
{
if (units.numDecimals == 0) {
return parseInt(edit.text);
}
return parseFloat(edit.text);
}
function clampValue(newValue)
{
if (units.wraps) {
// integer wrapping for now
var range = (units.maxValue - units.minValue + 1);
if (newValue < units.minValue) newValue += range;
if (newValue > units.maxValue) newValue -= range;
}
return Math.min(Math.max(units.minValue, newValue), units.maxValue);
}
function incrementValue()
{
if (edit.activeFocus) {
var newValue = clampValue(parseTextAsValue() + units.stepSize);
edit.text = newValue
if (live) {
doCommit(newValue);
}
} else {
doCommit(clampValue(quantity.value + units.stepSize));
}
}
function decrementValue()
{
if (edit.activeFocus) {
var newValue = clampValue(parseTextAsValue() - units.stepSize);
edit.text = newValue
if (live) {
doCommit(newValue);
}
} else {
doCommit(clampValue(quantity.value - units.stepSize));
}
}
Component.onCompleted: {
if (quantity.unit === Units.NoUnits) {
var q = quantity;
q.unit = units.selectedUnit;
commit(q);
}
}
onQuantityChanged: {
// ensure our units model is in sync
units.selectedUnit = quantity.unit
}
TextMetrics {
id: metrics
text: units.maxTextForMetrics
}
StyledText {
id: label
text: root.label
anchors.verticalCenter: editFrame.verticalCenter
hover: editFrame.activeFocus
enabled: root.enabled
}
MouseArea {
height: root.height
width: root.width
enabled: root.enabled
// use wheel events to adjust up/dowm
onClicked: {
edit.forceActiveFocus();
}
onWheel: {
var delta = wheel.angleDelta.y
if (delta > 0) {
root.incrementValue()
} else if (delta < 0) {
root.decrementValue()
}
}
}
// timer to commit the value when in live mode
Timer {
id: liveEditTimer
interval: 800
onTriggered: {
if (edit.activeFocus) {
doCommit(parseTextAsValue());
}
}
}
Binding {
when: !edit.activeFocus
target: edit
property: "text"
value: root.quantity.value.toFixed(units.numDecimals)
}
Rectangle {
id: editFrame
clip: true
anchors.left: label.right
anchors.margins: Style.margin
height: edit.implicitHeight + Style.margin
width: edit.width + prefix.width + suffix.width + upDownArea.width + Style.margin * 2
radius: Style.roundRadius
border.color: root.enable ? (edit.activeFocus ? Style.frameColor : Style.minorFrameColor) : Style.disabledMinorFrameColor
border.width: 1
ClickableText {
id: prefix
visible: units.isPrefix
enabled: root.enabled
anchors.baseline: edit.baseline
anchors.left: parent.left
anchors.margins: Style.margin
text: visible ? units.shortText : ""
onClicked: unitSelectionPopup.show()
clickable: (units.numChoices > 1)
}
TextInput {
id: edit
enabled: root.enabled
anchors.verticalCenter: parent.verticalCenter
anchors.left: prefix.right
selectByMouse: true
width: metrics.width
horizontalAlignment: Text.AlignRight
font.pixelSize: Style.baseFontPixelSize
focus: true
color: enabled ? (activeFocus ? Style.themeColor : Style.baseTextColor) : Style.disabledTextColor
validator: units.validator
Keys.onUpPressed: {
root.incrementValue();
}
Keys.onDownPressed: {
root.decrementValue();
}
onActiveFocusChanged: {
if (activeFocus) {
selectAll();
} else {
doCommit(parseTextAsValue())
liveEditTimer.stop();
}
}
onTextChanged: {
if (activeFocus && root.live) {
liveEditTimer.restart();
}
}
}
ClickableText {
id: suffix
visible: !units.isPrefix
enabled: root.enabled
anchors.baseline: edit.baseline
anchors.right: upDownArea.left
text: visible ? units.shortText : ""
onClicked: unitSelectionPopup.show()
clickable: (units.numChoices > 1)
}
Item {
id: upDownArea
// color: "white"
anchors.right: parent.right
anchors.rightMargin: Style.margin
anchors.verticalCenter: editFrame.verticalCenter
height: upDownIcon.implicitHeight
visible: edit.activeFocus
width: upDownIcon.implicitWidth
Image {
id: upDownIcon
// show up/down arrows
source: "qrc:///up-down-arrow"
}
MouseArea {
width: parent.width
height: parent.height / 2
onPressed: {
root.incrementValue();
}
Rectangle {
anchors.fill: parent
opacity: 0.5
color: Style.themeColor
visible: parent.pressed
}
Timer {
id: upRepeat
interval: 250
running: parent.pressed
repeat: true
onTriggered: root.incrementValue()
}
}
MouseArea {
width: parent.width
height: parent.height / 2
anchors.bottom: parent.bottom
onPressed: {
root.decrementValue();
}
Rectangle {
anchors.fill: parent
opacity: 0.5
color: Style.themeColor
visible: parent.pressed
}
Timer {
id: downRepeat
interval: 250
running: parent.pressed
repeat: true
onTriggered: root.decrementValue()
}
}
}
} // of frame rectangle
PopupWindowTracker {
id: tracker
}
Window {
id: unitSelectionPopup
visible: false
flags: Qt.Popup
color: "white"
height: choicesColumn.childrenRect.height + Style.margin * 2
width: choicesColumn.width + Style.margin * 2
function show()
{
var screenPos = _launcher.mapToGlobal(editFrame, Qt.point(0, editFrame.height))
unitSelectionPopup.x = screenPos.x;
unitSelectionPopup.y = screenPos.y;
unitSelectionPopup.visible = true
tracker.window = unitSelectionPopup
}
Rectangle {
border.width: 1
border.color: Style.minorFrameColor
anchors.fill: parent
}
// choice layout column
Column {
id: choicesColumn
spacing: Style.margin
x: Style.margin
y: Style.margin
width: menuWidth
function calculateMenuWidth()
{
var minWidth = 0;
for (var i = 0; i < choicesRepeater.count; i++) {
minWidth = Math.max(minWidth, choicesRepeater.itemAt(i).implicitWidth);
}
return minWidth;
}
readonly property int menuWidth: calculateMenuWidth()
// main item repeater
Repeater {
id: choicesRepeater
model: units
delegate:
Text {
id: choiceText
readonly property bool selected: units.selectedIndex === model.index
text: model.longName
height: implicitHeight + Style.margin
font.pixelSize: Style.baseFontPixelSize
color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor
MouseArea {
id: choiceArea
width: unitSelectionPopup.width // full width of the popup
height: parent.height
hoverEnabled: true
onClicked: {
units.selectedIndex = model.index;
root.commit(root.quantity.convertToUnit(units.selectedUnit));
unitSelectionPopup.visible = false;
}
}
} // of Text delegate
} // text repeater
} // text column
}
}

View file

@ -41,7 +41,7 @@ Item {
radius: Style.roundRadius * 1.5
anchors.verticalCenter: parent.verticalCenter
color: (checked && enabled) ? Style.themeColor : "white"
color: enabled && (checked | mouseArea.containsMouse) ? Style.themeColor : "white"
border.width: 1
border.color: Style.inactiveThemeColor
@ -72,5 +72,7 @@ Item {
hoverEnabled: true
onClicked: root.toggle(!checked)
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}

View file

@ -114,6 +114,7 @@
<file>qml/HeaderBox.qml</file>
<file>qml/TimeEdit.qml</file>
<file>qml/AirportEntry.qml</file>
<file>qml/NumericalEdit.qml</file>
</qresource>
<qresource prefix="/preview">
<file alias="close-icon">preview-close.png</file>