1
0
Fork 0
flightgear/src/GUI/FlightPlanController.cxx
Slawek Mikula 354d7d02d2 #2177 - launcher - added flightplan info to summary window:
- 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
2021-01-18 12:38:58 +00:00

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"