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