354d7d02d2
- Summary.qml - new row with Flight Plan info - LauncherController.hxx, Launcher.qml - signals to show flightPlan & exposing flight plan object - FlightPlan.qml - add red color to information about airport mismatch - FlightPlanController.cxx, FlightPlanController.hxx - description handling, support to loading flight-plan from command line argument 'flight-plan', saving last used dir when loading/saving flightplans
629 lines
16 KiB
C++
629 lines
16 KiB
C++
#include "FlightPlanController.hxx"
|
|
|
|
#include <Main/options.hxx>
|
|
#include <QAbstractListModel>
|
|
#include <QDebug>
|
|
#include <QFileDialog>
|
|
#include <QQmlComponent>
|
|
#include <QSettings>
|
|
#include <QTimer>
|
|
|
|
#include <simgear/misc/sg_path.hxx>
|
|
|
|
#include <Main/globals.hxx>
|
|
#include <Navaids/waypoint.hxx>
|
|
#include <Navaids/airways.hxx>
|
|
#include <Navaids/navrecord.hxx>
|
|
#include <Navaids/airways.hxx>
|
|
|
|
#include "QmlPositioned.hxx"
|
|
#include "LaunchConfig.hxx"
|
|
|
|
using namespace flightgear;
|
|
|
|
const int LegDistanceRole = Qt::UserRole;
|
|
const int LegTrackRole = Qt::UserRole + 1;
|
|
const int LegTerminatorNavRole = Qt::UserRole + 2;
|
|
const int LegAirwayIdentRole = Qt::UserRole + 3;
|
|
const int LegTerminatorTypeRole = Qt::UserRole + 4;
|
|
const int LegTerminatorNavNameRole = Qt::UserRole + 5;
|
|
const int LegTerminatorNavFrequencyRole = Qt::UserRole + 6;
|
|
const int LegAltitudeFtRole = Qt::UserRole + 7;
|
|
const int LegAltitudeTypeRole = Qt::UserRole + 8;
|
|
|
|
class LegsModel : public QAbstractListModel
|
|
{
|
|
Q_OBJECT
|
|
|
|
Q_PROPERTY(int numLegs READ numLegs NOTIFY numLegsChanged)
|
|
public:
|
|
void setFlightPlan(flightgear::FlightPlanRef f)
|
|
{
|
|
beginResetModel();
|
|
_fp = f;
|
|
endResetModel();
|
|
emit numLegsChanged();
|
|
}
|
|
|
|
int rowCount(const QModelIndex &parent) const override
|
|
{
|
|
Q_UNUSED(parent)
|
|
return _fp->numLegs();
|
|
}
|
|
|
|
QVariant data(const QModelIndex &index, int role) const override
|
|
{
|
|
const auto leg = _fp->legAtIndex(index.row());
|
|
if (!leg)
|
|
return {};
|
|
|
|
const auto wp = leg->waypoint();
|
|
|
|
switch (role) {
|
|
case Qt::DisplayRole: {
|
|
if (wp->type() == "via") {
|
|
// we want the end waypoint name
|
|
return QString::fromStdString(wp->source()->ident());
|
|
}
|
|
|
|
return QString::fromStdString(leg->waypoint()->ident());
|
|
}
|
|
|
|
case LegDistanceRole:
|
|
return QVariant::fromValue(QuantityValue{Units::NauticalMiles, leg->distanceNm()});
|
|
case LegTrackRole:
|
|
return QVariant::fromValue(QuantityValue{Units::DegreesTrue, leg->courseDeg()});
|
|
|
|
case LegAirwayIdentRole:
|
|
{
|
|
AirwayRef awy;
|
|
if (wp->type() == "via") {
|
|
auto via = static_cast<flightgear::Via*>(leg->waypoint());
|
|
awy = via->airway();
|
|
} else if (wp->flag(WPT_VIA)) {
|
|
awy = static_cast<Airway*>(wp->owner());
|
|
}
|
|
|
|
return awy ? QString::fromStdString(awy->ident()) : QVariant{};
|
|
}
|
|
|
|
case LegTerminatorNavRole:
|
|
{
|
|
if (leg->waypoint()->source()) {
|
|
return QString::fromStdString(leg->waypoint()->source()->ident());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LegTerminatorNavFrequencyRole:
|
|
{
|
|
const auto n = fgpositioned_cast<FGNavRecord>(leg->waypoint()->source());
|
|
if (n) {
|
|
const double f = n->get_freq() / 100.0;
|
|
if (n->type() == FGPositioned::NDB) {
|
|
return QVariant::fromValue(QuantityValue(Units::FreqKHz, f));
|
|
}
|
|
|
|
return QVariant::fromValue(QuantityValue(Units::FreqMHz, f));
|
|
}
|
|
return QVariant::fromValue(QuantityValue());
|
|
}
|
|
|
|
case LegTerminatorNavNameRole:
|
|
{
|
|
if (leg->waypoint()->source()) {
|
|
return QString::fromStdString(leg->waypoint()->source()->name());
|
|
}
|
|
return QString{}; // avoud undefined-value QML error if we return a null variant
|
|
}
|
|
|
|
case LegTerminatorTypeRole:
|
|
return QString::fromStdString(leg->waypoint()->type());
|
|
|
|
case LegAltitudeFtRole:
|
|
return leg->altitudeFt();
|
|
|
|
case LegAltitudeTypeRole:
|
|
return leg->altitudeRestriction();
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void waypointsChanged()
|
|
{
|
|
beginResetModel();
|
|
endResetModel();
|
|
numLegsChanged();
|
|
}
|
|
|
|
QHash<int, QByteArray> roleNames() const override
|
|
{
|
|
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
|
|
|
result[Qt::DisplayRole] = "label";
|
|
result[LegDistanceRole] = "distance";
|
|
result[LegTrackRole] = "track";
|
|
result[LegTerminatorNavRole] = "to";
|
|
result[LegTerminatorNavFrequencyRole] = "frequency";
|
|
result[LegAirwayIdentRole] = "via";
|
|
result[LegTerminatorTypeRole] = "wpType";
|
|
result[LegTerminatorNavNameRole] = "toName";
|
|
result[LegAltitudeFtRole] = "altitudeFt";
|
|
result[LegAltitudeTypeRole] = "altitudeType";
|
|
|
|
return result;
|
|
}
|
|
|
|
int numLegs() const
|
|
{
|
|
return _fp->numLegs();
|
|
}
|
|
|
|
signals:
|
|
void numLegsChanged();
|
|
|
|
private:
|
|
flightgear::FlightPlanRef _fp;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
class FPDelegate : public FlightPlan::Delegate
|
|
{
|
|
public:
|
|
void arrivalChanged() override
|
|
{
|
|
p->infoChanged();
|
|
}
|
|
|
|
void departureChanged() override
|
|
{
|
|
p->infoChanged();
|
|
}
|
|
|
|
void cruiseChanged() override
|
|
{
|
|
p->infoChanged();
|
|
}
|
|
|
|
void waypointsChanged() override
|
|
{
|
|
QTimer::singleShot(0, p->_legs, &LegsModel::waypointsChanged);
|
|
p->waypointsChanged();
|
|
p->infoChanged();
|
|
}
|
|
|
|
FlightPlanController* p;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
FlightPlanController::FlightPlanController(QObject *parent, LaunchConfig* config)
|
|
: QObject(parent)
|
|
{
|
|
_config = config;
|
|
connect(_config, &LaunchConfig::collect, this, &FlightPlanController::onCollectConfig);
|
|
connect(_config, &LaunchConfig::save, this, &FlightPlanController::onSave);
|
|
connect(_config, &LaunchConfig::restore, this, &FlightPlanController::onRestore);
|
|
|
|
_delegate.reset(new FPDelegate);
|
|
_delegate->p = this; // link back to us
|
|
|
|
qmlRegisterUncreatableType<LegsModel>("FlightGear", 1, 0, "LegsModel", "singleton");
|
|
_fp.reset(new flightgear::FlightPlan);
|
|
_fp->addDelegate(_delegate.get());
|
|
_legs = new LegsModel();
|
|
_legs->setFlightPlan(_fp);
|
|
|
|
// initial restore
|
|
onRestore();
|
|
}
|
|
|
|
FlightPlanController::~FlightPlanController()
|
|
{
|
|
_fp->removeDelegate(_delegate.get());
|
|
}
|
|
|
|
void FlightPlanController::clearPlan()
|
|
{
|
|
auto fp = new flightgear::FlightPlan;
|
|
_fp->removeDelegate(_delegate.get());
|
|
_fp = fp;
|
|
_fp->addDelegate(_delegate.get());
|
|
_legs->setFlightPlan(fp);
|
|
emit infoChanged();
|
|
|
|
_enabled = false;
|
|
emit enabledChanged(_enabled);
|
|
}
|
|
|
|
bool FlightPlanController::loadFromPath(QString path)
|
|
{
|
|
auto fp = new flightgear::FlightPlan;
|
|
bool ok = fp->load(SGPath(path.toUtf8().data()));
|
|
if (!ok) {
|
|
qWarning() << "Failed to load flightplan " << path;
|
|
return false;
|
|
}
|
|
|
|
_fp->removeDelegate(_delegate.get());
|
|
_fp = fp;
|
|
_fp->addDelegate(_delegate.get());
|
|
_legs->setFlightPlan(fp);
|
|
|
|
_enabled = true;
|
|
emit enabledChanged(_enabled);
|
|
|
|
// notify that everything changed
|
|
emit infoChanged();
|
|
return true;
|
|
}
|
|
|
|
bool FlightPlanController::saveToPath(QString path) const
|
|
{
|
|
SGPath p(path.toUtf8().data());
|
|
return _fp->save(p);
|
|
}
|
|
|
|
void FlightPlanController::onCollectConfig()
|
|
{
|
|
if (!_enabled)
|
|
return;
|
|
|
|
SGPath p = globals->get_fg_home() / "launcher.fgfp";
|
|
_fp->save(p);
|
|
|
|
_config->setArg("flight-plan", p.utf8Str());
|
|
}
|
|
|
|
void FlightPlanController::onSave()
|
|
{
|
|
std::ostringstream ss;
|
|
_fp->save(ss);
|
|
_config->setValueForKey("", "fp", QString::fromStdString(ss.str()));
|
|
}
|
|
|
|
void FlightPlanController::onRestore()
|
|
{
|
|
_enabled = _config->getValueForKey("", "fp-enabled", false).toBool();
|
|
emit enabledChanged(_enabled);
|
|
|
|
// if the user specified --flight-plan, load that one
|
|
auto options = flightgear::Options::sharedInstance();
|
|
std::string fpArgPath = options->valueForOption("flight-plan");
|
|
SGPath fp = SGPath::fromUtf8(fpArgPath);
|
|
if (fp.exists()) {
|
|
loadFromPath(QString::fromStdString(fpArgPath));
|
|
} else {
|
|
std::string planXML = _config->getValueForKey("", "fp", QString()).toString().toStdString();
|
|
if (!planXML.empty()) {
|
|
std::istringstream ss(planXML);
|
|
_fp->load(ss);
|
|
emit infoChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
QuantityValue FlightPlanController::cruiseAltitude() const
|
|
{
|
|
if (_fp->cruiseFlightLevel() > 0)
|
|
return {Units::FlightLevel, _fp->cruiseFlightLevel()};
|
|
|
|
return {Units::FeetMSL, _fp->cruiseAltitudeFt()};
|
|
}
|
|
|
|
void FlightPlanController::setCruiseAltitude(QuantityValue alt)
|
|
{
|
|
const int ival = static_cast<int>(alt.value);
|
|
if (alt.unit == Units::FlightLevel) {
|
|
if (_fp->cruiseFlightLevel() == ival) {
|
|
return;
|
|
}
|
|
|
|
_fp->setCruiseFlightLevel(ival);
|
|
} else if (alt.unit == Units::FeetMSL) {
|
|
if (_fp->cruiseAltitudeFt() == ival) {
|
|
return;
|
|
}
|
|
|
|
_fp->setCruiseAltitudeFt(ival);
|
|
}
|
|
|
|
emit infoChanged();
|
|
}
|
|
|
|
QString FlightPlanController::description() const
|
|
{
|
|
if (_fp->numLegs() == 0) {
|
|
return tr("No flight-plan");
|
|
}
|
|
|
|
return tr("From %1 (%2) to %3 (%4)")
|
|
.arg(departure()->ident())
|
|
.arg(departure()->name())
|
|
.arg(destination()->ident())
|
|
.arg(destination()->name());
|
|
}
|
|
|
|
QmlPositioned *FlightPlanController::departure() const
|
|
{
|
|
if (!_fp->departureAirport())
|
|
return new QmlPositioned;
|
|
|
|
return new QmlPositioned(_fp->departureAirport());
|
|
}
|
|
|
|
QmlPositioned *FlightPlanController::destination() const
|
|
{
|
|
if (!_fp->destinationAirport())
|
|
return new QmlPositioned;
|
|
|
|
return new QmlPositioned(_fp->destinationAirport());
|
|
}
|
|
|
|
QmlPositioned *FlightPlanController::alternate() const
|
|
{
|
|
if (!_fp->alternate())
|
|
return new QmlPositioned;
|
|
|
|
return new QmlPositioned(_fp->alternate());
|
|
}
|
|
|
|
QuantityValue FlightPlanController::cruiseSpeed() const
|
|
{
|
|
if (_fp->cruiseSpeedMach() > 0.0) {
|
|
return {Units::Mach, _fp->cruiseSpeedMach()};
|
|
}
|
|
|
|
return {Units::Knots, _fp->cruiseSpeedKnots()};
|
|
}
|
|
|
|
FlightPlanController::FlightRules FlightPlanController::flightRules() const
|
|
{
|
|
return static_cast<FlightRules>(_fp->flightRules());
|
|
}
|
|
|
|
FlightPlanController::FlightType FlightPlanController::flightType() const
|
|
{
|
|
return static_cast<FlightType>(_fp->flightType());
|
|
}
|
|
|
|
void FlightPlanController::setFlightRules(FlightRules r)
|
|
{
|
|
_fp->setFlightRules(static_cast<flightgear::ICAOFlightRules>(r));
|
|
}
|
|
|
|
void FlightPlanController::setFlightType(FlightType ty)
|
|
{
|
|
_fp->setFlightType(static_cast<flightgear::ICAOFlightType>(ty));
|
|
}
|
|
|
|
QString FlightPlanController::callsign() const
|
|
{
|
|
return QString::fromStdString(_fp->callsign());
|
|
}
|
|
|
|
QString FlightPlanController::remarks() const
|
|
{
|
|
return QString::fromStdString(_fp->remarks());
|
|
}
|
|
|
|
QString FlightPlanController::aircraftType() const
|
|
{
|
|
return QString::fromStdString(_fp->icaoAircraftType());
|
|
}
|
|
|
|
void FlightPlanController::setCallsign(QString s)
|
|
{
|
|
const auto stdS = s.toStdString();
|
|
if (_fp->callsign() == stdS)
|
|
return;
|
|
|
|
_fp->setCallsign(stdS);
|
|
emit infoChanged();
|
|
}
|
|
|
|
void FlightPlanController::setRemarks(QString r)
|
|
{
|
|
const auto stdR = r.toStdString();
|
|
if (_fp->remarks() == stdR)
|
|
return;
|
|
|
|
_fp->setRemarks(stdR);
|
|
emit infoChanged();
|
|
}
|
|
|
|
void FlightPlanController::setAircraftType(QString ty)
|
|
{
|
|
const auto stdT = ty.toStdString();
|
|
if (_fp->icaoAircraftType() == stdT)
|
|
return;
|
|
|
|
_fp->setIcaoAircraftType(stdT);
|
|
emit infoChanged();
|
|
}
|
|
|
|
int FlightPlanController::estimatedDurationMinutes() const
|
|
{
|
|
return _fp->estimatedDurationMinutes();
|
|
}
|
|
|
|
QuantityValue FlightPlanController::totalDistanceNm() const
|
|
{
|
|
return QuantityValue{Units::NauticalMiles, _fp->totalDistanceNm()};
|
|
}
|
|
|
|
bool FlightPlanController::tryParseRoute(QString routeDesc)
|
|
{
|
|
bool ok = _fp->parseICAORouteString(routeDesc.toStdString());
|
|
return ok;
|
|
}
|
|
|
|
bool FlightPlanController::tryGenerateRoute()
|
|
{
|
|
if (!_fp->departureAirport() || !_fp->destinationAirport()) {
|
|
qWarning() << "departure or destination not set";
|
|
|
|
return false;
|
|
}
|
|
|
|
auto net = Airway::highLevel();
|
|
auto fromNode = net->findClosestNode(_fp->departureAirport()->geod());
|
|
auto toNode = net->findClosestNode(_fp->destinationAirport()->geod());
|
|
if (!fromNode.first) {
|
|
qWarning() << "Couldn't find airway network transition for "
|
|
<< QString::fromStdString(_fp->departureAirport()->ident());
|
|
return false;
|
|
}
|
|
|
|
if (!toNode.first) {
|
|
qWarning() << "Couldn't find airway network transition for "
|
|
<< QString::fromStdString(_fp->destinationAirport()->ident());
|
|
return false;
|
|
}
|
|
|
|
WayptRef fromWp = new NavaidWaypoint(fromNode.first, _fp);
|
|
WayptRef toWp = new NavaidWaypoint(toNode.first, _fp);
|
|
WayptVec path;
|
|
bool ok = net->route(fromWp, toWp, path);
|
|
if (!ok) {
|
|
qWarning() << "unable to find a route";
|
|
return false;
|
|
}
|
|
|
|
_fp->clearLegs();
|
|
_fp->insertWayptAtIndex(fromWp, -1);
|
|
_fp->insertWayptsAtIndex(path, -1);
|
|
_fp->insertWayptAtIndex(toWp, -1);
|
|
|
|
return true;
|
|
}
|
|
|
|
void FlightPlanController::clearRoute()
|
|
{
|
|
_fp->clearAll();
|
|
}
|
|
|
|
QString FlightPlanController::icaoRoute() const
|
|
{
|
|
return QString::fromStdString(_fp->asICAORouteString());
|
|
}
|
|
|
|
void FlightPlanController::setEstimatedDurationMinutes(int mins)
|
|
{
|
|
if (_fp->estimatedDurationMinutes() == mins)
|
|
return;
|
|
|
|
_fp->setEstimatedDurationMinutes(mins);
|
|
emit infoChanged();
|
|
}
|
|
|
|
void FlightPlanController::computeDuration()
|
|
{
|
|
_fp->computeDurationMinutes();
|
|
emit infoChanged();
|
|
}
|
|
|
|
bool FlightPlanController::loadPlan()
|
|
{
|
|
QSettings settings;
|
|
QString lastUsedDir = settings.value("flightplan-lastdir", "").toString();
|
|
|
|
QString file = QFileDialog::getOpenFileName(nullptr, tr("Load a flight-plan"),
|
|
lastUsedDir, "*.fgfp *.gpx");
|
|
if (file.isEmpty())
|
|
return false;
|
|
|
|
QFileInfo fi(file);
|
|
settings.setValue("flightplan-lastdir", fi.absolutePath());
|
|
|
|
return loadFromPath(file);
|
|
}
|
|
|
|
void FlightPlanController::savePlan()
|
|
{
|
|
QSettings settings;
|
|
QString lastUsedDir = settings.value("flightplan-lastdir", "").toString();
|
|
|
|
QString file = QFileDialog::getSaveFileName(nullptr, tr("Save flight-plan"),
|
|
lastUsedDir, "*.fgfp");
|
|
if (file.isEmpty())
|
|
return;
|
|
if (!file.endsWith(".fgfp")) {
|
|
file += ".fgfp";
|
|
}
|
|
|
|
QFileInfo fi(file);
|
|
settings.setValue("flightplan-lastdir", fi.absolutePath());
|
|
|
|
saveToPath(file);
|
|
}
|
|
|
|
void FlightPlanController::setDeparture(QmlPositioned *apt)
|
|
{
|
|
if (!apt) {
|
|
_fp->clearDeparture();
|
|
} else {
|
|
if (apt->inner() == _fp->departureAirport())
|
|
return;
|
|
|
|
_fp->setDeparture(fgpositioned_cast<FGAirport>(apt->inner()));
|
|
}
|
|
|
|
emit infoChanged();
|
|
}
|
|
|
|
void FlightPlanController::setDestination(QmlPositioned *apt)
|
|
{
|
|
if (apt) {
|
|
if (apt->inner() == _fp->destinationAirport())
|
|
return;
|
|
|
|
_fp->setDestination(fgpositioned_cast<FGAirport>(apt->inner()));
|
|
} else {
|
|
_fp->clearDestination();
|
|
|
|
}
|
|
emit infoChanged();
|
|
}
|
|
|
|
void FlightPlanController::setAlternate(QmlPositioned *apt)
|
|
{
|
|
if (apt) {
|
|
if (apt->inner() == _fp->alternate())
|
|
return;
|
|
|
|
_fp->setAlternate(fgpositioned_cast<FGAirport>(apt->inner()));
|
|
} else {
|
|
_fp->setAlternate(nullptr);
|
|
|
|
}
|
|
emit infoChanged();
|
|
}
|
|
|
|
void FlightPlanController::setCruiseSpeed(QuantityValue speed)
|
|
{
|
|
qInfo() << Q_FUNC_INFO << speed.unit << speed.value;
|
|
if (speed.unit == Units::Mach) {
|
|
if (speed == QuantityValue(Units::Mach, _fp->cruiseSpeedMach())) {
|
|
return;
|
|
}
|
|
|
|
_fp->setCruiseSpeedMach(speed.value);
|
|
} else if (speed.unit == Units::Knots) {
|
|
const int knotsVal = static_cast<int>(speed.value);
|
|
if (_fp->cruiseSpeedKnots() == knotsVal) {
|
|
return;
|
|
}
|
|
|
|
_fp->setCruiseSpeedKnots(knotsVal);
|
|
}
|
|
|
|
emit infoChanged();
|
|
}
|
|
|
|
#include "FlightPlanController.moc"
|