Flight planning in the launcher
Still evolving but usable to import or build a route now
This commit is contained in:
parent
331939f640
commit
df7e13d734
13 changed files with 1396 additions and 8 deletions
|
@ -157,10 +157,14 @@ if (HAVE_QT)
|
|||
UnitsModel.hxx
|
||||
NavaidSearchModel.hxx
|
||||
NavaidSearchModel.cxx
|
||||
FlightPlanController.cxx
|
||||
FlightPlanController.hxx
|
||||
RouteDiagram.cxx
|
||||
RouteDiagram.hxx
|
||||
)
|
||||
|
||||
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)
|
||||
target_link_libraries(fgqmlui Qt5::Quick Qt5::Network Qt5::Qml SimGearCore)
|
||||
target_link_libraries(fgqmlui Qt5::Quick Qt5::Widgets Qt5::Network Qt5::Qml SimGearCore)
|
||||
target_include_directories(fgqmlui PRIVATE ${PROJECT_BINARY_DIR}/src/GUI)
|
||||
add_dependencies(fgqmlui fgfs_qm_files)
|
||||
|
||||
|
|
552
src/GUI/FlightPlanController.cxx
Normal file
552
src/GUI/FlightPlanController.cxx
Normal file
|
@ -0,0 +1,552 @@
|
|||
#include "FlightPlanController.hxx"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
#include <QFileDialog>
|
||||
#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;
|
||||
|
||||
class LegsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
void setFlightPlan(flightgear::FlightPlanRef f)
|
||||
{
|
||||
beginResetModel();
|
||||
_fp = f;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
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 {};
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
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:
|
||||
{
|
||||
const auto wp = leg->waypoint();
|
||||
if (wp->type() == "via") {
|
||||
auto via = static_cast<flightgear::Via*>(leg->waypoint());
|
||||
return QString::fromStdString(via->airway());
|
||||
}
|
||||
|
||||
if (wp->flag(WPT_VIA)) {
|
||||
AirwayRef awy = static_cast<Airway*>(wp->owner());
|
||||
return QString::fromStdString(awy->ident());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LegTerminatorTypeRole:
|
||||
return QString::fromStdString(leg->waypoint()->type());
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void waypointsChanged()
|
||||
{
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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->clear();
|
||||
_fp->insertWayptAtIndex(fromWp, -1);
|
||||
_fp->insertWayptsAtIndex(path, -1);
|
||||
_fp->insertWayptAtIndex(toWp, -1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlightPlanController::clearRoute()
|
||||
{
|
||||
_fp->clear();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(nullptr, tr("Load a flight-plan"),
|
||||
{}, "*.fgfp");
|
||||
if (file.isEmpty())
|
||||
return false;
|
||||
|
||||
return loadFromPath(file);
|
||||
}
|
||||
|
||||
void FlightPlanController::savePlan()
|
||||
{
|
||||
QString file = QFileDialog::getSaveFileName(nullptr, tr("Save flight-plan"),
|
||||
{}, "*.fgfp");
|
||||
if (file.isEmpty())
|
||||
return;
|
||||
if (!file.endsWith(".fgfp")) {
|
||||
file += ".fgfp";
|
||||
}
|
||||
|
||||
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"
|
148
src/GUI/FlightPlanController.hxx
Normal file
148
src/GUI/FlightPlanController.hxx
Normal file
|
@ -0,0 +1,148 @@
|
|||
#ifndef FLIGHTPLANCONTROLLER_HXX
|
||||
#define FLIGHTPLANCONTROLLER_HXX
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <Navaids/FlightPlan.hxx>
|
||||
|
||||
#include "UnitsModel.hxx"
|
||||
|
||||
class QmlPositioned;
|
||||
class LegsModel;
|
||||
class FPDelegate;
|
||||
class LaunchConfig;
|
||||
|
||||
class FlightPlanController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString callsign READ callsign WRITE setCallsign NOTIFY infoChanged)
|
||||
Q_PROPERTY(QString remarks READ remarks WRITE setRemarks NOTIFY infoChanged)
|
||||
Q_PROPERTY(QString aircraftType READ aircraftType WRITE setAircraftType NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(LegsModel* legs READ legs CONSTANT)
|
||||
|
||||
Q_PROPERTY(QString icaoRoute READ icaoRoute NOTIFY waypointsChanged)
|
||||
|
||||
Q_ENUMS(FlightRules)
|
||||
Q_ENUMS(FlightType)
|
||||
|
||||
Q_PROPERTY(FlightRules flightRules READ flightRules WRITE setFlightRules NOTIFY infoChanged)
|
||||
Q_PROPERTY(FlightType flightType READ flightType WRITE setFlightType NOTIFY infoChanged)
|
||||
|
||||
// planned departure date + time
|
||||
|
||||
Q_PROPERTY(QuantityValue totalDistanceNm READ totalDistanceNm NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(int estimatedDurationMinutes READ estimatedDurationMinutes WRITE setEstimatedDurationMinutes NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(QuantityValue cruiseAltitude READ cruiseAltitude WRITE setCruiseAltitude NOTIFY infoChanged)
|
||||
Q_PROPERTY(QuantityValue cruiseSpeed READ cruiseSpeed WRITE setCruiseSpeed NOTIFY infoChanged)
|
||||
|
||||
Q_PROPERTY(QmlPositioned* departure READ departure WRITE setDeparture NOTIFY infoChanged)
|
||||
Q_PROPERTY(QmlPositioned* destination READ destination WRITE setDestination NOTIFY infoChanged)
|
||||
Q_PROPERTY(QmlPositioned* alternate READ alternate WRITE setAlternate NOTIFY infoChanged)
|
||||
|
||||
// equipment
|
||||
public:
|
||||
virtual ~FlightPlanController();
|
||||
|
||||
// alias these enums to QML
|
||||
enum FlightRules
|
||||
{
|
||||
VFR = 0,
|
||||
IFR,
|
||||
IFR_VFR,
|
||||
VFR_IFR
|
||||
};
|
||||
|
||||
enum FlightType
|
||||
{
|
||||
Scheduled = 0,
|
||||
NonScheduled,
|
||||
GeneralAviation,
|
||||
Military,
|
||||
Other
|
||||
};
|
||||
|
||||
explicit FlightPlanController(QObject *parent,
|
||||
LaunchConfig* config);
|
||||
|
||||
bool loadFromPath(QString path);
|
||||
bool saveToPath(QString path) const;
|
||||
|
||||
QuantityValue cruiseAltitude() const;
|
||||
void setCruiseAltitude(QuantityValue alt);
|
||||
|
||||
QmlPositioned* departure() const;
|
||||
QmlPositioned* destination() const;
|
||||
QmlPositioned* alternate() const;
|
||||
|
||||
QuantityValue cruiseSpeed() const;
|
||||
|
||||
FlightRules flightRules() const;
|
||||
FlightType flightType() const;
|
||||
|
||||
QString callsign() const;
|
||||
QString remarks() const;
|
||||
QString aircraftType() const;
|
||||
|
||||
int estimatedDurationMinutes() const;
|
||||
QuantityValue totalDistanceNm() const;
|
||||
|
||||
Q_INVOKABLE bool tryParseRoute(QString routeDesc);
|
||||
|
||||
Q_INVOKABLE bool tryGenerateRoute();
|
||||
Q_INVOKABLE void clearRoute();
|
||||
|
||||
LegsModel* legs() const
|
||||
{ return _legs; }
|
||||
|
||||
QString icaoRoute() const;
|
||||
|
||||
flightgear::FlightPlanRef flightplan() const
|
||||
{ return _fp; }
|
||||
|
||||
Q_INVOKABLE bool loadPlan();
|
||||
signals:
|
||||
void infoChanged();
|
||||
void waypointsChanged();
|
||||
|
||||
public slots:
|
||||
|
||||
void setFlightType(FlightType ty);
|
||||
void setFlightRules(FlightRules r);
|
||||
|
||||
void setCallsign(QString s);
|
||||
void setRemarks(QString r);
|
||||
void setAircraftType(QString ty);
|
||||
|
||||
void setDeparture(QmlPositioned* destinationAirport);
|
||||
void setDestination(QmlPositioned* destinationAirport);
|
||||
void setAlternate(QmlPositioned* apt);
|
||||
|
||||
void setCruiseSpeed(QuantityValue cruiseSpeed);
|
||||
|
||||
void setEstimatedDurationMinutes(int mins);
|
||||
|
||||
void computeDuration();
|
||||
|
||||
void clearPlan();
|
||||
void savePlan();
|
||||
private slots:
|
||||
void onCollectConfig();
|
||||
void onSave();
|
||||
void onRestore();
|
||||
|
||||
private:
|
||||
friend class FPDelegate;
|
||||
|
||||
flightgear::FlightPlanRef _fp;
|
||||
LegsModel* _legs = nullptr;
|
||||
std::unique_ptr<FPDelegate> _delegate;
|
||||
LaunchConfig* _config = nullptr;
|
||||
};
|
||||
|
||||
#endif // FLIGHTPLANCONTROLLER_HXX
|
|
@ -46,9 +46,11 @@
|
|||
#include "PixmapImageItem.hxx"
|
||||
#include "AirportDiagram.hxx"
|
||||
#include "NavaidDiagram.hxx"
|
||||
#include "RouteDiagram.hxx"
|
||||
#include "QmlRadioButtonHelper.hxx"
|
||||
#include "UnitsModel.hxx"
|
||||
#include "NavaidSearchModel.hxx"
|
||||
#include "FlightPlanController.hxx"
|
||||
|
||||
using namespace simgear::pkg;
|
||||
|
||||
|
@ -66,6 +68,8 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
|
|||
connect(m_config, &LaunchConfig::save, this, &LauncherController::saveAircraft);
|
||||
connect(m_config, &LaunchConfig::restore, this, &LauncherController::restoreAircraft);
|
||||
|
||||
m_flightPlan = new FlightPlanController(this, m_config);
|
||||
|
||||
m_location->setLaunchConfig(m_config);
|
||||
connect(m_location, &LocationController::descriptionChanged,
|
||||
this, &LauncherController::summaryChanged);
|
||||
|
@ -126,6 +130,7 @@ void LauncherController::initQML()
|
|||
{
|
||||
qmlRegisterUncreatableType<LauncherController>("FlightGear.Launcher", 1, 0, "LauncherController", "no");
|
||||
qmlRegisterUncreatableType<LocationController>("FlightGear.Launcher", 1, 0, "LocationController", "no");
|
||||
qmlRegisterUncreatableType<FlightPlanController>("FlightGear.Launcher", 1, 0, "FlightPlanController", "no");
|
||||
|
||||
qmlRegisterType<LauncherArgumentTokenizer>("FlightGear.Launcher", 1, 0, "ArgumentTokenizer");
|
||||
qmlRegisterUncreatableType<QAbstractItemModel>("FlightGear.Launcher", 1, 0, "QAIM", "no");
|
||||
|
@ -156,6 +161,7 @@ void LauncherController::initQML()
|
|||
qmlRegisterType<PixmapImageItem>("FlightGear", 1, 0, "PixmapImage");
|
||||
qmlRegisterType<AirportDiagram>("FlightGear", 1, 0, "AirportDiagram");
|
||||
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
|
||||
qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram");
|
||||
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
|
||||
|
||||
qmlRegisterSingletonType(QUrl("qrc:///qml/OverlayShared.qml"), "FlightGear", 1, 0, "OverlayShared");
|
||||
|
|
|
@ -41,6 +41,7 @@ class AircraftItemModel;
|
|||
class QQuickItem;
|
||||
class LaunchConfig;
|
||||
class LocationController;
|
||||
class FlightPlanController;
|
||||
|
||||
class LauncherController : public QObject
|
||||
{
|
||||
|
@ -54,6 +55,7 @@ class LauncherController : public QObject
|
|||
Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT)
|
||||
|
||||
Q_PROPERTY(LocationController* location MEMBER m_location CONSTANT)
|
||||
Q_PROPERTY(FlightPlanController* flightPlan MEMBER m_flightPlan CONSTANT)
|
||||
|
||||
Q_PROPERTY(MPServersModel* mpServersModel MEMBER m_serversModel CONSTANT)
|
||||
|
||||
|
@ -246,6 +248,7 @@ private:
|
|||
AircraftProxyModel* m_aircraftWithUpdatesModel;
|
||||
MPServersModel* m_serversModel = nullptr;
|
||||
LocationController* m_location = nullptr;
|
||||
FlightPlanController* m_flightPlan = nullptr;
|
||||
|
||||
QUrl m_selectedAircraft;
|
||||
QString m_aircraftState;
|
||||
|
|
141
src/GUI/RouteDiagram.cxx
Normal file
141
src/GUI/RouteDiagram.cxx
Normal file
|
@ -0,0 +1,141 @@
|
|||
// RouteDiagram.cxx - GUI diagram of a route
|
||||
//
|
||||
// Written by James Turner, started August 2018.
|
||||
//
|
||||
// Copyright (C) 2018 James Turner <james@flightgear.org>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#include "RouteDiagram.hxx"
|
||||
|
||||
|
||||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
#include <QVector2D>
|
||||
#include <QMouseEvent>
|
||||
|
||||
#include <Navaids/NavDataCache.hxx>
|
||||
|
||||
#include "FlightPlanController.hxx"
|
||||
|
||||
using namespace flightgear;
|
||||
|
||||
RouteDiagram::RouteDiagram(QQuickItem* pr) :
|
||||
BaseDiagram(pr)
|
||||
{
|
||||
}
|
||||
|
||||
void RouteDiagram::setFlightplan(FlightPlanController *fp)
|
||||
{
|
||||
if (fp == m_flightplan)
|
||||
return;
|
||||
|
||||
if (m_flightplan) {
|
||||
// disconnect from old signal
|
||||
disconnect(m_flightplan, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
m_flightplan = fp;
|
||||
emit flightplanChanged(fp);
|
||||
|
||||
if (fp) {
|
||||
connect(fp, &FlightPlanController::infoChanged, this, &RouteDiagram::fpChanged);
|
||||
connect(fp, &FlightPlanController::waypointsChanged, this, &RouteDiagram::fpChanged);
|
||||
}
|
||||
|
||||
fpChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
int RouteDiagram::numLegs() const
|
||||
{
|
||||
if (!m_flightplan)
|
||||
return 0;
|
||||
|
||||
FlightPlanRef fp = m_flightplan->flightplan();
|
||||
if (!fp)
|
||||
return 0;
|
||||
|
||||
return fp->numLegs();
|
||||
}
|
||||
|
||||
void RouteDiagram::setActiveLegIndex(int activeLegIndex)
|
||||
{
|
||||
if (m_activeLegIndex == activeLegIndex)
|
||||
return;
|
||||
|
||||
m_activeLegIndex = activeLegIndex;
|
||||
emit legIndexChanged(m_activeLegIndex);
|
||||
|
||||
const double halfLegDistance = m_path->distanceForIndex(m_activeLegIndex) * 0.5;
|
||||
m_projectionCenter = m_path->positionForDistanceFrom(m_activeLegIndex, halfLegDistance);
|
||||
recomputeBounds(true);
|
||||
update();
|
||||
}
|
||||
|
||||
void RouteDiagram::paintContents(QPainter *painter)
|
||||
{
|
||||
if (!m_flightplan)
|
||||
return;
|
||||
|
||||
FlightPlanRef fp = m_flightplan->flightplan();
|
||||
QVector<QLineF> lines;
|
||||
QVector<QLineF> activeLines;
|
||||
for (int l=0; l < fp->numLegs(); ++l) {
|
||||
QPointF previous;
|
||||
bool isFirst = true;
|
||||
for (auto g : m_path->pathForIndex(l)) {
|
||||
QPointF p = project(g);
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else if (l == m_activeLegIndex) {
|
||||
activeLines.append(QLineF(previous, p));
|
||||
} else {
|
||||
lines.append(QLineF(previous, p));
|
||||
}
|
||||
previous = p;
|
||||
}
|
||||
}
|
||||
|
||||
QPen linePen(Qt::magenta, 2);
|
||||
linePen.setCosmetic(true);
|
||||
painter->setPen(linePen);
|
||||
painter->drawLines(lines);
|
||||
|
||||
linePen.setColor(Qt::yellow);
|
||||
painter->setPen(linePen);
|
||||
painter->drawLines(activeLines);
|
||||
}
|
||||
|
||||
void RouteDiagram::doComputeBounds()
|
||||
{
|
||||
FlightPlanRef fp = m_flightplan->flightplan();
|
||||
const SGGeodVec gv(m_path->pathForIndex(m_activeLegIndex));
|
||||
std::for_each(gv.begin(), gv.end(), [this](const SGGeod& g)
|
||||
{this->extendBounds(this->project(g)); }
|
||||
);
|
||||
}
|
||||
|
||||
void RouteDiagram::fpChanged()
|
||||
{
|
||||
FlightPlanRef fp = m_flightplan->flightplan();
|
||||
m_path.reset(new RoutePath(fp));
|
||||
if (fp) {
|
||||
const double halfLegDistance = m_path->distanceForIndex(m_activeLegIndex) * 0.5;
|
||||
m_projectionCenter = m_path->positionForDistanceFrom(m_activeLegIndex, halfLegDistance);
|
||||
}
|
||||
recomputeBounds(true);
|
||||
update();
|
||||
}
|
81
src/GUI/RouteDiagram.hxx
Normal file
81
src/GUI/RouteDiagram.hxx
Normal file
|
@ -0,0 +1,81 @@
|
|||
// RouteDiagram.hxx - show a route graphically
|
||||
//
|
||||
// Written by James Turner, started August 2018.
|
||||
//
|
||||
// Copyright (C) 2018 James Turner <james@flightgear.org>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#ifndef GUI_ROUTE_DIAGRAM_HXX
|
||||
#define GUI_ROUTE_DIAGRAM_HXX
|
||||
|
||||
#include "BaseDiagram.hxx"
|
||||
#include "QmlPositioned.hxx"
|
||||
#include "UnitsModel.hxx"
|
||||
|
||||
#include <Navaids/navrecord.hxx>
|
||||
#include <Navaids/routePath.hxx>
|
||||
|
||||
#include <simgear/math/sg_geodesy.hxx>
|
||||
|
||||
class FlightPlanController;
|
||||
|
||||
class RouteDiagram : public BaseDiagram
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(FlightPlanController* flightplan READ flightplan WRITE setFlightplan NOTIFY flightplanChanged)
|
||||
|
||||
Q_PROPERTY(int activeLegIndex READ activeLegIndex WRITE setActiveLegIndex NOTIFY legIndexChanged)
|
||||
Q_PROPERTY(int numLegs READ numLegs NOTIFY flightplanChanged)
|
||||
public:
|
||||
RouteDiagram(QQuickItem* pr = nullptr);
|
||||
|
||||
FlightPlanController* flightplan() const
|
||||
{
|
||||
return m_flightplan;
|
||||
}
|
||||
|
||||
void setFlightplan(FlightPlanController* fp);
|
||||
|
||||
int numLegs() const;
|
||||
|
||||
int activeLegIndex() const
|
||||
{
|
||||
return m_activeLegIndex;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void setActiveLegIndex(int activeLegIndex);
|
||||
|
||||
signals:
|
||||
void flightplanChanged(FlightPlanController* flightplan);
|
||||
|
||||
void legIndexChanged(int activeLegIndex);
|
||||
|
||||
protected:
|
||||
void paintContents(QPainter *) override;
|
||||
|
||||
void doComputeBounds() override;
|
||||
private:
|
||||
void fpChanged();
|
||||
|
||||
FlightPlanController* m_flightplan = nullptr;
|
||||
|
||||
std::unique_ptr<RoutePath> m_path;
|
||||
int m_activeLegIndex = 0;
|
||||
};
|
||||
|
||||
#endif // of GUI_ROUTE_DIAGRAM_HXX
|
|
@ -10,15 +10,58 @@ Item {
|
|||
height: parent.height
|
||||
width: parent.width - scrollbar.width
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: contents.childrenRect.height
|
||||
contentHeight: contents.childrenRect.height + Style.margin * 2
|
||||
|
||||
Component.onCompleted: {
|
||||
if (_launcher.flightPlan.cruiseSpeed.value === 0.0) {
|
||||
_launcher.flightPlan.cruiseSpeed = _launcher.selectedAircraftInfo.cruiseSpeed
|
||||
}
|
||||
|
||||
if (_launcher.flightPlan.cruiseAltitude.value === 0.0) {
|
||||
_launcher.flightPlan.cruiseAltitude = _launcher.selectedAircraftInfo.cruiseAltitude
|
||||
}
|
||||
|
||||
_launcher.flightPlan.aircraftType = _launcher.selectedAircraftInfo.icaoType
|
||||
route.text = _launcher.flightPlan.icaoRoute
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: contents
|
||||
width: parent.width - (Style.margin * 2)
|
||||
x: Style.margin
|
||||
y: Style.margin
|
||||
spacing: Style.margin
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Style.margin
|
||||
height: childrenRect.height
|
||||
|
||||
Button {
|
||||
text: qsTr("Load");
|
||||
onClicked: {
|
||||
var ok = _launcher.flightPlan.loadPlan();
|
||||
if (ok) {
|
||||
route.text = _launcher.flightPlan.icaoRoute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Save");
|
||||
onClicked: _launcher.flightPlan.savePlan();
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Clear");
|
||||
onClicked: {
|
||||
_launcher.flightPlan.clearPlan();
|
||||
route.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HeaderBox {
|
||||
title: qsTr("Aircraft & flight information")
|
||||
width: parent.width
|
||||
|
@ -35,13 +78,17 @@ Item {
|
|||
text: qsTr("Callsign / Flight No.")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
LineEdit {
|
||||
// Aircraft identication - callsign (share with MP)
|
||||
|
||||
LineEdit {
|
||||
id: aircraftIdent
|
||||
placeholder: "D-FGFS"
|
||||
suggestedWidthString: "XXXXXX";
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: _launcher.flightPlan.callsign
|
||||
|
||||
onTextChanged: {
|
||||
_launcher.flightPlan.callsign = text
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: Style.strutSize; height: 1 }
|
||||
|
@ -54,6 +101,11 @@ Item {
|
|||
placeholder: "B738"
|
||||
suggestedWidthString: "XXXX";
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: _launcher.flightPlan.aircraftType
|
||||
|
||||
onTextChanged: {
|
||||
_launcher.flightPlan.aircraftType = text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +118,14 @@ Item {
|
|||
id: flightRules
|
||||
label: qsTr("Flight rules:")
|
||||
model: ["VFR", "IFR"] // initially IFR (Y), initially VFR (Z)
|
||||
|
||||
Component.onCompleted: {
|
||||
select(_launcher.flightPlan.flightRules);
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
_launcher.flightPlan.flightRules = currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: Style.strutSize; height: 1 }
|
||||
|
@ -78,6 +138,14 @@ Item {
|
|||
qsTr("General aviation"),
|
||||
qsTr("Military"),
|
||||
qsTr("Other")]
|
||||
|
||||
Component.onCompleted: {
|
||||
select(_launcher.flightPlan.flightType);
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
_launcher.flightPlan.flightType = currentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,6 +179,22 @@ Item {
|
|||
|
||||
AirportEntry {
|
||||
label: qsTr("Departure airport:")
|
||||
|
||||
Component.onCompleted: {
|
||||
selectAirport(_launcher.flightPlan.departure.guid)
|
||||
}
|
||||
|
||||
onPickAirport: {
|
||||
selectAirport(guid)
|
||||
_launcher.flightPlan.departure = airport
|
||||
}
|
||||
|
||||
onClickedName: {
|
||||
detailLoader.airportGuid = airport.guid
|
||||
detailLoader.sourceComponent = airportDetails;
|
||||
}
|
||||
|
||||
KeyNavigation.tab: departureTime
|
||||
}
|
||||
|
||||
// padding
|
||||
|
@ -132,14 +216,23 @@ Item {
|
|||
NumericalEdit {
|
||||
label: qsTr("Cruise speed:")
|
||||
unitsMode: Units.Speed
|
||||
quantity: _launcher.flightPlan.cruiseSpeed
|
||||
onCommit: {
|
||||
_launcher.flightPlan.cruiseSpeed = newValue
|
||||
}
|
||||
KeyNavigation.tab: cruiseAltitude
|
||||
|
||||
}
|
||||
|
||||
// padding
|
||||
Item { width: Style.strutSize; height: 1 }
|
||||
|
||||
NumericalEdit {
|
||||
id: cruiseAltitude
|
||||
label: qsTr("Cruise altitude:")
|
||||
unitsMode: Units.AltitudeIncludingMeters
|
||||
quantity: _launcher.flightPlan.cruiseAltitude
|
||||
onCommit: _launcher.flightPlan.cruiseAltitude = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +244,69 @@ Item {
|
|||
PlainTextEditBox {
|
||||
id: route
|
||||
width: parent.width
|
||||
enabled: _launcher.flightPlan.departure.valid && _launcher.flightPlan.destination.valid
|
||||
|
||||
onEditingFinished: {
|
||||
var ok = _launcher.flightPlan.tryParseRoute(text);
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
height: generateRouteButton.height
|
||||
width: parent.width
|
||||
spacing: Style.margin
|
||||
|
||||
Button {
|
||||
id: generateRouteButton
|
||||
text: qsTr("Generate route")
|
||||
enabled: route.enabled
|
||||
onClicked: {
|
||||
var ok = _launcher.flightPlan.tryGenerateRoute();
|
||||
if (ok) {
|
||||
route.text = _launcher.flightPlan.icaoRoute;
|
||||
}
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
PopupChoice {
|
||||
id: routeNetwork
|
||||
label: qsTr("Using")
|
||||
model: [qsTr("High-level (Jet) airways"),
|
||||
qsTr("Low-level (Victor) airways"),
|
||||
qsTr("High- & low-level airways")]
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("View route")
|
||||
onClicked: {
|
||||
detailLoader.airportGuid = 0
|
||||
detailLoader.sourceComponent = routeDetails;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Clear route")
|
||||
onClicked: {
|
||||
_launcher.flightPlan.clearRoute();
|
||||
route.text = "";
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
RouteLegsView
|
||||
{
|
||||
id: legsView
|
||||
width: parent.width
|
||||
|
||||
onClickedLeg: {
|
||||
detailLoader.airportGuid = 0
|
||||
detailLoader.legIndex = index
|
||||
detailLoader.sourceComponent = routeDetails;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -162,6 +317,20 @@ Item {
|
|||
AirportEntry {
|
||||
id: destinationICAO
|
||||
label: qsTr("Destination airport:")
|
||||
|
||||
Component.onCompleted: {
|
||||
selectAirport(_launcher.flightPlan.destination.guid)
|
||||
}
|
||||
|
||||
onPickAirport: {
|
||||
selectAirport(guid)
|
||||
_launcher.flightPlan.destination = airport
|
||||
}
|
||||
|
||||
onClickedName: {
|
||||
detailLoader.airportGuid = airport.guid
|
||||
detailLoader.sourceComponent = airportDetails;
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: Style.strutSize; height: 1 }
|
||||
|
@ -169,15 +338,47 @@ Item {
|
|||
TimeEdit {
|
||||
id: enrouteEstimate
|
||||
label: qsTr("Estimated enroute time:")
|
||||
|
||||
Component.onCompleted: {
|
||||
setDurationMinutes(_launcher.flightPlan.estimatedDurationMinutes)
|
||||
}
|
||||
|
||||
onValueChanged: {
|
||||
_launcher.flightPlan.estimatedDurationMinutes = value.getHours() * 60 + value.getMinutes();
|
||||
}
|
||||
}
|
||||
|
||||
Item { width: Style.strutSize; height: 1 }
|
||||
|
||||
StyledText
|
||||
{
|
||||
text: qsTr("Total distance: %1").arg(_launcher.flightPlan.totalDistanceNm);
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
height: childrenRect.height
|
||||
width: parent.width
|
||||
spacing: Style.margin
|
||||
|
||||
AirportEntry {
|
||||
id: alternate1
|
||||
label: qsTr("Alternate airport:")
|
||||
|
||||
Component.onCompleted: {
|
||||
selectAirport(_launcher.flightPlan.alternate.guid)
|
||||
}
|
||||
|
||||
onPickAirport: {
|
||||
selectAirport(guid)
|
||||
_launcher.flightPlan.alternate = airport
|
||||
}
|
||||
|
||||
onClickedName: {
|
||||
detailLoader.airportGuid = airport.guid
|
||||
detailLoader.sourceComponent = airportDetails;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HeaderBox {
|
||||
|
@ -193,10 +394,12 @@ Item {
|
|||
PlainTextEditBox {
|
||||
id: remarks
|
||||
width: parent.width
|
||||
text: _launcher.flightPlan.remarks
|
||||
|
||||
onEditingFinished: {
|
||||
_launcher.flightPlan.remarks = text;
|
||||
}
|
||||
}
|
||||
|
||||
// speak to Act-pie guy about passing all this over MP props?
|
||||
} // of main column
|
||||
|
||||
} // of flickable
|
||||
|
@ -208,4 +411,50 @@ Item {
|
|||
flickable: flick
|
||||
visible: flick.contentHeight > flick.height
|
||||
}
|
||||
|
||||
Component {
|
||||
id: airportDetails
|
||||
PlanAirportView {
|
||||
id: airportView
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: routeDetails
|
||||
PlanRouteDetails {
|
||||
id: routeView
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: detailLoader
|
||||
anchors.fill: parent
|
||||
visible: sourceComponent != null
|
||||
|
||||
property var airportGuid
|
||||
property int legIndex
|
||||
|
||||
onStatusChanged: {
|
||||
if (status == Loader.Ready) {
|
||||
if (item.hasOwnProperty("location")) {
|
||||
item.location = airportGuid
|
||||
}
|
||||
|
||||
if (item.hasOwnProperty("legIndex")) {
|
||||
item.legIndex = legIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
anchors { left: parent.left; top: parent.top; margins: Style.margin }
|
||||
width: Style.strutSize
|
||||
visible: detailLoader.visible
|
||||
text: "< Back"
|
||||
onClicked: {
|
||||
detailLoader.sourceComponent = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ Item {
|
|||
MenuItem { text:qsTr("Save configuration as..."); shortcut: "Ctrl+S";
|
||||
onTriggered: _launcher.saveConfigAs(); },
|
||||
MenuDivider {},
|
||||
MenuItem { text:qsTr("Flight-planning"); onTriggered: root.enterFlightPlan(); shortcut: "Ctrl+P"; enabled: false},
|
||||
MenuItem { text:qsTr("Flight-planning"); onTriggered: root.enterFlightPlan(); shortcut: "Ctrl+P"; enabled: true},
|
||||
MenuDivider {},
|
||||
MenuItem { text:qsTr("View command line"); onTriggered: _launcher.viewCommandLine(); shortcut: "Ctrl+L"},
|
||||
MenuItem { text:qsTr("Select data files location..."); onTriggered: _launcher.requestChangeDataPath(); },
|
||||
|
|
28
src/GUI/qml/PlanAirportView.qml
Normal file
28
src/GUI/qml/PlanAirportView.qml
Normal file
|
@ -0,0 +1,28 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear 1.0
|
||||
import FlightGear.Launcher 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property alias location: airportData.guid
|
||||
|
||||
Positioned {
|
||||
id: airportData
|
||||
}
|
||||
|
||||
|
||||
AirportDiagram {
|
||||
id: diagram
|
||||
anchors.fill: parent
|
||||
airport: airportData.guid
|
||||
|
||||
onClicked: {
|
||||
if (pos === null)
|
||||
return;
|
||||
}
|
||||
|
||||
approachExtensionEnabled: false
|
||||
}
|
||||
|
||||
}
|
43
src/GUI/qml/PlanRouteDetails.qml
Normal file
43
src/GUI/qml/PlanRouteDetails.qml
Normal file
|
@ -0,0 +1,43 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear 1.0
|
||||
import FlightGear.Launcher 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias legIndex: diagram.activeLegIndex
|
||||
|
||||
RouteDiagram {
|
||||
id: diagram
|
||||
anchors.fill: parent
|
||||
flightplan: _launcher.flightPlan
|
||||
}
|
||||
|
||||
Button {
|
||||
id: previousButton
|
||||
text: qsTr("Previous Leg")
|
||||
enabled: diagram.activeLegIndex > 0
|
||||
onClicked: {
|
||||
diagram.activeLegIndex = diagram.activeLegIndex - 1
|
||||
}
|
||||
|
||||
anchors.right: root.horizontalCenter
|
||||
anchors.bottom: root.bottom
|
||||
anchors.margins: Style.margin
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Next Leg")
|
||||
width: previousButton.width
|
||||
|
||||
enabled: diagram.activeLegIndex < (diagram.numLegs - 1)
|
||||
onClicked: {
|
||||
diagram.activeLegIndex = diagram.activeLegIndex + 1
|
||||
}
|
||||
|
||||
anchors.left: root.horizontalCenter
|
||||
anchors.bottom: root.bottom
|
||||
anchors.margins: Style.margin
|
||||
}
|
||||
}
|
130
src/GUI/qml/RouteLegsView.qml
Normal file
130
src/GUI/qml/RouteLegsView.qml
Normal file
|
@ -0,0 +1,130 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear.Launcher 1.0
|
||||
import FlightGear 1.0
|
||||
import "."
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: root
|
||||
implicitHeight: childrenRect.height + Style.margin * 2
|
||||
|
||||
border.width: 1
|
||||
border.color: Style.minorFrameColor
|
||||
|
||||
signal clickedLeg(var index)
|
||||
|
||||
TextMetrics {
|
||||
id: legDistanceWidth
|
||||
font.pixelSize: Style.baseFontPixelSize
|
||||
text: "0000Nm"
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: legBearingWidth
|
||||
font.pixelSize: Style.baseFontPixelSize
|
||||
text: "000*True"
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: legIdentWidth
|
||||
font.pixelSize: Style.baseFontPixelSize
|
||||
text: "XXXXX"
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: legViaWidth
|
||||
font.pixelSize: Style.baseFontPixelSize
|
||||
text: "via XXXXX"
|
||||
}
|
||||
|
||||
readonly property int legDistanceColumnStart: root.width - (legDistanceWidth.width + (Style.margin * 2))
|
||||
readonly property int legBearingColumnStart: legDistanceColumnStart - (legBearingWidth.width + Style.margin)
|
||||
|
||||
// description string fills the middle space, gets elided
|
||||
readonly property int legDescriptionColumnStart: legIdentWidth.width + legViaWidth.width + Style.margin * 3
|
||||
readonly property int legDescriptStringWidth: legBearingColumnStart - legDescriptionColumnStart
|
||||
|
||||
Column {
|
||||
width: parent.width - Style.margin * 2
|
||||
x: Style.margin
|
||||
y: Style.margin
|
||||
|
||||
Repeater {
|
||||
id: routeLegs
|
||||
width: parent.width
|
||||
|
||||
model: _launcher.flightPlan.legs
|
||||
|
||||
delegate: Rectangle {
|
||||
id: delegateRect
|
||||
height: rowLabel.height + Style.margin
|
||||
width: routeLegs.width
|
||||
color: (model.index % 2) ? "#dfdfdf" : "white"
|
||||
|
||||
readonly property string description: {
|
||||
var s = model.toName;
|
||||
if (model.wpType === "navaid") {
|
||||
var freq = model.frequency
|
||||
if (freq.isValid())
|
||||
s += " (" + freq.toString() + ")"
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: rowLabel
|
||||
text: model.label
|
||||
x: Style.margin
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: rowAirway
|
||||
text: {
|
||||
var awy = model.via;
|
||||
if (awy === undefined) return "";
|
||||
return "via " + awy;
|
||||
}
|
||||
|
||||
x: Style.margin * 2 + legIdentWidth.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: model.wpType === "navaid"
|
||||
text: delegateRect.description
|
||||
x: legDescriptionColumnStart
|
||||
width: legDescriptStringWidth
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
x: legBearingColumnStart
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: (model.index > 0)
|
||||
text: model.track.toString()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
x: legDistanceColumnStart
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: (model.index > 0)
|
||||
text: model.distance.toString()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.clickedLeg(model.index)
|
||||
}
|
||||
}
|
||||
|
||||
} // of delegate rect
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -118,6 +118,9 @@
|
|||
<file>qml/Overlay.qml</file>
|
||||
<file>qml/OverlayShared.qml</file>
|
||||
<file>qml/Weblink.qml</file>
|
||||
<file>qml/PlanAirportView.qml</file>
|
||||
<file>qml/PlanRouteDetails.qml</file>
|
||||
<file>qml/RouteLegsView.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/preview">
|
||||
<file alias="close-icon">preview-close.png</file>
|
||||
|
|
Loading…
Reference in a new issue