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