Better units handling in the launcher / UI
This commit is contained in:
parent
9716274612
commit
9098219032
19 changed files with 1176 additions and 286 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -152,6 +152,8 @@ if (HAVE_QT)
|
|||
QmlNavCacheWrapper.cxx
|
||||
QmlRadioButtonHelper.cxx
|
||||
QmlRadioButtonHelper.hxx
|
||||
UnitsModel.cxx
|
||||
UnitsModel.hxx
|
||||
)
|
||||
|
||||
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
411
src/GUI/UnitsModel.cxx
Normal 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
177
src/GUI/UnitsModel.hxx
Normal 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
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
359
src/GUI/qml/NumericalEdit.qml
Normal file
359
src/GUI/qml/NumericalEdit.qml
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue