1 #include "FlightPlanController.hxx"
2 
3 #include <Main/options.hxx>
4 #include <QAbstractListModel>
5 #include <QDebug>
6 #include <QFileDialog>
7 #include <QQmlComponent>
8 #include <QSettings>
9 #include <QTimer>
10 
11 #include <simgear/misc/sg_path.hxx>
12 
13 #include <Main/globals.hxx>
14 #include <Navaids/waypoint.hxx>
15 #include <Navaids/airways.hxx>
16 #include <Navaids/navrecord.hxx>
17 #include <Navaids/airways.hxx>
18 
19 #include "QmlPositioned.hxx"
20 #include "LaunchConfig.hxx"
21 
22 using namespace flightgear;
23 
24 const int LegDistanceRole = Qt::UserRole;
25 const int LegTrackRole = Qt::UserRole + 1;
26 const int LegTerminatorNavRole = Qt::UserRole + 2;
27 const int LegAirwayIdentRole = Qt::UserRole + 3;
28 const int LegTerminatorTypeRole = Qt::UserRole + 4;
29 const int LegTerminatorNavNameRole = Qt::UserRole + 5;
30 const int LegTerminatorNavFrequencyRole = Qt::UserRole + 6;
31 const int LegAltitudeFtRole = Qt::UserRole + 7;
32 const int LegAltitudeTypeRole = Qt::UserRole + 8;
33 
34 class LegsModel : public QAbstractListModel
35 {
36     Q_OBJECT
37 
38     Q_PROPERTY(int numLegs READ numLegs NOTIFY numLegsChanged)
39 public:
setFlightPlan(flightgear::FlightPlanRef f)40     void setFlightPlan(flightgear::FlightPlanRef f)
41     {
42         beginResetModel();
43         _fp = f;
44         endResetModel();
45         emit numLegsChanged();
46     }
47 
rowCount(const QModelIndex & parent) const48     int rowCount(const QModelIndex &parent) const override
49     {
50         Q_UNUSED(parent)
51         return _fp->numLegs();
52     }
53 
data(const QModelIndex & index,int role) const54     QVariant data(const QModelIndex &index, int role) const override
55     {
56         const auto leg = _fp->legAtIndex(index.row());
57         if (!leg)
58             return {};
59 
60         const auto wp = leg->waypoint();
61 
62         switch (role) {
63         case Qt::DisplayRole: {
64             if (wp->type() == "via") {
65                 // we want the end waypoint name
66                 return QString::fromStdString(wp->source()->ident());
67             }
68 
69             return QString::fromStdString(leg->waypoint()->ident());
70         }
71 
72         case LegDistanceRole:
73             return QVariant::fromValue(QuantityValue{Units::NauticalMiles, leg->distanceNm()});
74         case LegTrackRole:
75             return QVariant::fromValue(QuantityValue{Units::DegreesTrue, leg->courseDeg()});
76 
77         case LegAirwayIdentRole:
78         {
79             AirwayRef awy;
80             if (wp->type() == "via") {
81                 auto via = static_cast<flightgear::Via*>(leg->waypoint());
82                 awy = via->airway();
83             } else if (wp->flag(WPT_VIA)) {
84                 awy = static_cast<Airway*>(wp->owner());
85             }
86 
87             return awy ? QString::fromStdString(awy->ident()) : QVariant{};
88         }
89 
90         case LegTerminatorNavRole:
91         {
92             if (leg->waypoint()->source()) {
93                 return QString::fromStdString(leg->waypoint()->source()->ident());
94             }
95             break;
96         }
97 
98         case LegTerminatorNavFrequencyRole:
99         {
100             const auto n = fgpositioned_cast<FGNavRecord>(leg->waypoint()->source());
101             if (n) {
102                 const double f = n->get_freq() / 100.0;
103                 if (n->type() == FGPositioned::NDB) {
104                     return QVariant::fromValue(QuantityValue(Units::FreqKHz, f));
105                 }
106 
107                 return QVariant::fromValue(QuantityValue(Units::FreqMHz, f));
108             }
109             return QVariant::fromValue(QuantityValue());
110         }
111 
112         case LegTerminatorNavNameRole:
113         {
114             if (leg->waypoint()->source()) {
115                 return QString::fromStdString(leg->waypoint()->source()->name());
116             }
117             return QString{}; // avoud undefined-value QML error if we return a null variant
118         }
119 
120         case LegTerminatorTypeRole:
121             return QString::fromStdString(leg->waypoint()->type());
122 
123         case LegAltitudeFtRole:
124             return leg->altitudeFt();
125 
126         case LegAltitudeTypeRole:
127             return leg->altitudeRestriction();
128 
129         default:
130             break;
131         }
132 
133         return {};
134     }
135 
waypointsChanged()136     void waypointsChanged()
137     {
138         beginResetModel();
139         endResetModel();
140         numLegsChanged();
141     }
142 
roleNames() const143     QHash<int, QByteArray> roleNames() const override
144     {
145         QHash<int, QByteArray> result = QAbstractListModel::roleNames();
146 
147         result[Qt::DisplayRole] = "label";
148         result[LegDistanceRole] = "distance";
149         result[LegTrackRole] = "track";
150         result[LegTerminatorNavRole] = "to";
151         result[LegTerminatorNavFrequencyRole] = "frequency";
152         result[LegAirwayIdentRole] = "via";
153         result[LegTerminatorTypeRole] = "wpType";
154         result[LegTerminatorNavNameRole] = "toName";
155         result[LegAltitudeFtRole] = "altitudeFt";
156         result[LegAltitudeTypeRole] = "altitudeType";
157 
158         return result;
159     }
160 
numLegs() const161     int numLegs() const
162     {
163         return _fp->numLegs();
164     }
165 
166 signals:
167     void numLegsChanged();
168 
169 private:
170     flightgear::FlightPlanRef _fp;
171 };
172 
173 /////////////////////////////////////////////////////////////////////////////
174 
175 class FPDelegate : public FlightPlan::Delegate
176 {
177 public:
arrivalChanged()178     void arrivalChanged() override
179     {
180         p->infoChanged();
181     }
182 
departureChanged()183     void departureChanged() override
184     {
185         p->infoChanged();
186     }
187 
cruiseChanged()188     void cruiseChanged() override
189     {
190         p->infoChanged();
191     }
192 
waypointsChanged()193     void waypointsChanged() override
194     {
195         QTimer::singleShot(0, p->_legs, &LegsModel::waypointsChanged);
196         p->waypointsChanged();
197         p->infoChanged();
198     }
199 
200     FlightPlanController* p;
201 };
202 
203 /////////////////////////////////////////////////////////////////////////////
204 
FlightPlanController(QObject * parent,LaunchConfig * config)205 FlightPlanController::FlightPlanController(QObject *parent, LaunchConfig* config)
206     : QObject(parent)
207 {
208     _config = config;
209     connect(_config, &LaunchConfig::collect, this, &FlightPlanController::onCollectConfig);
210     connect(_config, &LaunchConfig::save, this, &FlightPlanController::onSave);
211     connect(_config, &LaunchConfig::restore, this, &FlightPlanController::onRestore);
212 
213     _delegate.reset(new FPDelegate);
214     _delegate->p = this; // link back to us
215 
216     qmlRegisterUncreatableType<LegsModel>("FlightGear", 1, 0, "LegsModel", "singleton");
217     _fp.reset(new flightgear::FlightPlan);
218     _fp->addDelegate(_delegate.get());
219     _legs = new LegsModel();
220     _legs->setFlightPlan(_fp);
221 
222     // initial restore
223     onRestore();
224 }
225 
~FlightPlanController()226 FlightPlanController::~FlightPlanController()
227 {
228     _fp->removeDelegate(_delegate.get());
229 }
230 
clearPlan()231 void FlightPlanController::clearPlan()
232 {
233     auto fp = new flightgear::FlightPlan;
234     _fp->removeDelegate(_delegate.get());
235     _fp = fp;
236     _fp->addDelegate(_delegate.get());
237     _legs->setFlightPlan(fp);
238     emit infoChanged();
239 
240     _enabled = false;
241     emit enabledChanged(_enabled);
242 }
243 
loadFromPath(QString path)244 bool FlightPlanController::loadFromPath(QString path)
245 {
246     auto fp = new flightgear::FlightPlan;
247     bool ok = fp->load(SGPath(path.toUtf8().data()));
248     if (!ok) {
249         qWarning() << "Failed to load flightplan " << path;
250         return false;
251     }
252 
253     _fp->removeDelegate(_delegate.get());
254     _fp = fp;
255     _fp->addDelegate(_delegate.get());
256     _legs->setFlightPlan(fp);
257 
258     _enabled = true;
259     emit enabledChanged(_enabled);
260 
261     // notify that everything changed
262     emit infoChanged();
263     return true;
264 }
265 
saveToPath(QString path) const266 bool FlightPlanController::saveToPath(QString path) const
267 {
268     SGPath p(path.toUtf8().data());
269     return _fp->save(p);
270 }
271 
onCollectConfig()272 void FlightPlanController::onCollectConfig()
273 {
274     if (!_enabled)
275         return;
276 
277     SGPath p = globals->get_fg_home() / "launcher.fgfp";
278     _fp->save(p);
279 
280     _config->setArg("flight-plan", p.utf8Str());
281 }
282 
onSave()283 void FlightPlanController::onSave()
284 {
285     std::ostringstream ss;
286     _fp->save(ss);
287     _config->setValueForKey("", "fp", QString::fromStdString(ss.str()));
288 }
289 
onRestore()290 void FlightPlanController::onRestore()
291 {
292     _enabled = _config->getValueForKey("", "fp-enabled", false).toBool();
293     emit enabledChanged(_enabled);
294 
295     // if the user specified --flight-plan, load that one
296     auto options = flightgear::Options::sharedInstance();
297     std::string fpArgPath = options->valueForOption("flight-plan");
298     SGPath fp = SGPath::fromUtf8(fpArgPath);
299     if (fp.exists()) {
300         loadFromPath(QString::fromStdString(fpArgPath));
301     } else {
302         std::string planXML = _config->getValueForKey("", "fp", QString()).toString().toStdString();
303         if (!planXML.empty()) {
304             std::istringstream ss(planXML);
305             _fp->load(ss);
306             emit infoChanged();
307         }
308     }
309 }
310 
cruiseAltitude() const311 QuantityValue FlightPlanController::cruiseAltitude() const
312 {
313     if (_fp->cruiseFlightLevel() > 0)
314         return {Units::FlightLevel, _fp->cruiseFlightLevel()};
315 
316     return {Units::FeetMSL, _fp->cruiseAltitudeFt()};
317 }
318 
setCruiseAltitude(QuantityValue alt)319 void FlightPlanController::setCruiseAltitude(QuantityValue alt)
320 {
321     const int ival = static_cast<int>(alt.value);
322     if (alt.unit == Units::FlightLevel) {
323         if (_fp->cruiseFlightLevel() == ival) {
324             return;
325         }
326 
327         _fp->setCruiseFlightLevel(ival);
328     } else if (alt.unit == Units::FeetMSL) {
329         if (_fp->cruiseAltitudeFt() == ival) {
330             return;
331         }
332 
333         _fp->setCruiseAltitudeFt(ival);
334     }
335 
336     emit infoChanged();
337 }
338 
description() const339 QString FlightPlanController::description() const
340 {
341     if (_fp->numLegs() == 0) {
342         return tr("No flight-plan");
343     }
344 
345     return tr("From %1 (%2) to %3 (%4)")
346         .arg(departure()->ident())
347         .arg(departure()->name())
348         .arg(destination()->ident())
349         .arg(destination()->name());
350 }
351 
departure() const352 QmlPositioned *FlightPlanController::departure() const
353 {
354     if (!_fp->departureAirport())
355         return new QmlPositioned;
356 
357     return new QmlPositioned(_fp->departureAirport());
358 }
359 
destination() const360 QmlPositioned *FlightPlanController::destination() const
361 {
362     if (!_fp->destinationAirport())
363         return new QmlPositioned;
364 
365     return new QmlPositioned(_fp->destinationAirport());
366 }
367 
alternate() const368 QmlPositioned *FlightPlanController::alternate() const
369 {
370     if (!_fp->alternate())
371         return new QmlPositioned;
372 
373     return new QmlPositioned(_fp->alternate());
374 }
375 
cruiseSpeed() const376 QuantityValue FlightPlanController::cruiseSpeed() const
377 {
378     if (_fp->cruiseSpeedMach() > 0.0) {
379         return {Units::Mach, _fp->cruiseSpeedMach()};
380     }
381 
382     return {Units::Knots, _fp->cruiseSpeedKnots()};
383 }
384 
flightRules() const385 FlightPlanController::FlightRules FlightPlanController::flightRules() const
386 {
387     return static_cast<FlightRules>(_fp->flightRules());
388 }
389 
flightType() const390 FlightPlanController::FlightType FlightPlanController::flightType() const
391 {
392     return static_cast<FlightType>(_fp->flightType());
393 }
394 
setFlightRules(FlightRules r)395 void FlightPlanController::setFlightRules(FlightRules r)
396 {
397     _fp->setFlightRules(static_cast<flightgear::ICAOFlightRules>(r));
398 }
399 
setFlightType(FlightType ty)400 void FlightPlanController::setFlightType(FlightType ty)
401 {
402     _fp->setFlightType(static_cast<flightgear::ICAOFlightType>(ty));
403 }
404 
callsign() const405 QString FlightPlanController::callsign() const
406 {
407     return QString::fromStdString(_fp->callsign());
408 }
409 
remarks() const410 QString FlightPlanController::remarks() const
411 {
412     return QString::fromStdString(_fp->remarks());
413 }
414 
aircraftType() const415 QString FlightPlanController::aircraftType() const
416 {
417     return QString::fromStdString(_fp->icaoAircraftType());
418 }
419 
setCallsign(QString s)420 void FlightPlanController::setCallsign(QString s)
421 {
422     const auto stdS = s.toStdString();
423     if (_fp->callsign() == stdS)
424         return;
425 
426     _fp->setCallsign(stdS);
427     emit infoChanged();
428 }
429 
setRemarks(QString r)430 void FlightPlanController::setRemarks(QString r)
431 {
432     const auto stdR = r.toStdString();
433     if (_fp->remarks() == stdR)
434         return;
435 
436     _fp->setRemarks(stdR);
437     emit infoChanged();
438 }
439 
setAircraftType(QString ty)440 void FlightPlanController::setAircraftType(QString ty)
441 {
442     const auto stdT = ty.toStdString();
443     if (_fp->icaoAircraftType() == stdT)
444         return;
445 
446     _fp->setIcaoAircraftType(stdT);
447     emit infoChanged();
448 }
449 
estimatedDurationMinutes() const450 int FlightPlanController::estimatedDurationMinutes() const
451 {
452     return _fp->estimatedDurationMinutes();
453 }
454 
totalDistanceNm() const455 QuantityValue FlightPlanController::totalDistanceNm() const
456 {
457     return QuantityValue{Units::NauticalMiles, _fp->totalDistanceNm()};
458 }
459 
tryParseRoute(QString routeDesc)460 bool FlightPlanController::tryParseRoute(QString routeDesc)
461 {
462     bool ok = _fp->parseICAORouteString(routeDesc.toStdString());
463     return ok;
464 }
465 
tryGenerateRoute()466 bool FlightPlanController::tryGenerateRoute()
467 {
468     if (!_fp->departureAirport() || !_fp->destinationAirport()) {
469         qWarning() << "departure or destination not set";
470 
471         return false;
472     }
473 
474     auto net = Airway::highLevel();
475     auto fromNode = net->findClosestNode(_fp->departureAirport()->geod());
476     auto toNode = net->findClosestNode(_fp->destinationAirport()->geod());
477     if (!fromNode.first) {
478         qWarning() << "Couldn't find airway network transition for "
479                    << QString::fromStdString(_fp->departureAirport()->ident());
480         return false;
481     }
482 
483     if (!toNode.first) {
484         qWarning() << "Couldn't find airway network transition for "
485                    << QString::fromStdString(_fp->destinationAirport()->ident());
486         return false;
487     }
488 
489     WayptRef fromWp = new NavaidWaypoint(fromNode.first, _fp);
490     WayptRef toWp = new NavaidWaypoint(toNode.first, _fp);
491     WayptVec path;
492     bool ok = net->route(fromWp, toWp, path);
493     if (!ok) {
494         qWarning() << "unable to find a route";
495         return false;
496     }
497 
498     _fp->clearLegs();
499     _fp->insertWayptAtIndex(fromWp, -1);
500     _fp->insertWayptsAtIndex(path, -1);
501     _fp->insertWayptAtIndex(toWp, -1);
502 
503     return true;
504 }
505 
clearRoute()506 void FlightPlanController::clearRoute()
507 {
508     _fp->clearAll();
509 }
510 
icaoRoute() const511 QString FlightPlanController::icaoRoute() const
512 {
513     return QString::fromStdString(_fp->asICAORouteString());
514 }
515 
setEstimatedDurationMinutes(int mins)516 void FlightPlanController::setEstimatedDurationMinutes(int mins)
517 {
518     if (_fp->estimatedDurationMinutes() == mins)
519         return;
520 
521     _fp->setEstimatedDurationMinutes(mins);
522     emit infoChanged();
523 }
524 
computeDuration()525 void FlightPlanController::computeDuration()
526 {
527     _fp->computeDurationMinutes();
528     emit infoChanged();
529 }
530 
loadPlan()531 bool FlightPlanController::loadPlan()
532 {
533     QSettings settings;
534     QString lastUsedDir = settings.value("flightplan-lastdir", "").toString();
535 
536     QString file = QFileDialog::getOpenFileName(nullptr, tr("Load a flight-plan"),
537                                                 lastUsedDir, "*.fgfp *.gpx");
538     if (file.isEmpty())
539         return false;
540 
541     QFileInfo fi(file);
542     settings.setValue("flightplan-lastdir", fi.absolutePath());
543 
544     return loadFromPath(file);
545 }
546 
savePlan()547 void FlightPlanController::savePlan()
548 {
549     QSettings settings;
550     QString lastUsedDir = settings.value("flightplan-lastdir", "").toString();
551 
552     QString file = QFileDialog::getSaveFileName(nullptr, tr("Save flight-plan"),
553                                                 lastUsedDir, "*.fgfp");
554     if (file.isEmpty())
555         return;
556     if (!file.endsWith(".fgfp")) {
557         file += ".fgfp";
558     }
559 
560     QFileInfo fi(file);
561     settings.setValue("flightplan-lastdir", fi.absolutePath());
562 
563     saveToPath(file);
564 }
565 
setDeparture(QmlPositioned * apt)566 void FlightPlanController::setDeparture(QmlPositioned *apt)
567 {
568     if (!apt) {
569         _fp->clearDeparture();
570     } else {
571         if (apt->inner() == _fp->departureAirport())
572             return;
573 
574         _fp->setDeparture(fgpositioned_cast<FGAirport>(apt->inner()));
575     }
576 
577     emit infoChanged();
578 }
579 
setDestination(QmlPositioned * apt)580 void FlightPlanController::setDestination(QmlPositioned *apt)
581 {
582     if (apt) {
583         if (apt->inner() == _fp->destinationAirport())
584             return;
585 
586         _fp->setDestination(fgpositioned_cast<FGAirport>(apt->inner()));
587     } else {
588         _fp->clearDestination();
589 
590     }
591     emit infoChanged();
592 }
593 
setAlternate(QmlPositioned * apt)594 void FlightPlanController::setAlternate(QmlPositioned *apt)
595 {
596     if (apt) {
597         if (apt->inner() == _fp->alternate())
598             return;
599 
600         _fp->setAlternate(fgpositioned_cast<FGAirport>(apt->inner()));
601     } else {
602         _fp->setAlternate(nullptr);
603 
604     }
605     emit infoChanged();
606 }
607 
setCruiseSpeed(QuantityValue speed)608 void FlightPlanController::setCruiseSpeed(QuantityValue speed)
609 {
610     qInfo() << Q_FUNC_INFO << speed.unit << speed.value;
611     if (speed.unit == Units::Mach) {
612         if (speed == QuantityValue(Units::Mach, _fp->cruiseSpeedMach())) {
613             return;
614         }
615 
616         _fp->setCruiseSpeedMach(speed.value);
617     } else if (speed.unit == Units::Knots) {
618         const int knotsVal = static_cast<int>(speed.value);
619         if (_fp->cruiseSpeedKnots() == knotsVal) {
620             return;
621         }
622 
623         _fp->setCruiseSpeedKnots(knotsVal);
624     }
625 
626     emit infoChanged();
627 }
628 
629 #include "FlightPlanController.moc"
630