#include "FlightPlanController.hxx" #include
#include #include #include #include #include #include #include #include
#include #include #include #include #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(leg->waypoint()); awy = via->airway(); } else if (wp->flag(WPT_VIA)) { awy = static_cast(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(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 roleNames() const override { QHash 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("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(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(_fp->flightRules()); } FlightPlanController::FlightType FlightPlanController::flightType() const { return static_cast(_fp->flightType()); } void FlightPlanController::setFlightRules(FlightRules r) { _fp->setFlightRules(static_cast(r)); } void FlightPlanController::setFlightType(FlightType ty) { _fp->setFlightType(static_cast(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(apt->inner())); } emit infoChanged(); } void FlightPlanController::setDestination(QmlPositioned *apt) { if (apt) { if (apt->inner() == _fp->destinationAirport()) return; _fp->setDestination(fgpositioned_cast(apt->inner())); } else { _fp->clearDestination(); } emit infoChanged(); } void FlightPlanController::setAlternate(QmlPositioned *apt) { if (apt) { if (apt->inner() == _fp->alternate()) return; _fp->setAlternate(fgpositioned_cast(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(speed.value); if (_fp->cruiseSpeedKnots() == knotsVal) { return; } _fp->setCruiseSpeedKnots(knotsVal); } emit infoChanged(); } #include "FlightPlanController.moc"