Launcher: initial carrier support
Allow selecting carriers from scenarios, and starting at either a parking position, or a distance offset from the FLOLS (effectively a crude ‘on-final’) Extend the —carrier startup option to accept a runway ident of FLOLS, in conjunction with the existing —offset-distance argument.
This commit is contained in:
parent
f903cdfa50
commit
177fc565da
22 changed files with 962 additions and 171 deletions
|
@ -17,9 +17,7 @@
|
|||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
#include <config.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
@ -35,11 +33,13 @@
|
|||
|
||||
#include "AICarrier.hxx"
|
||||
|
||||
FGAICarrier::FGAICarrier() : FGAIShip(otCarrier), deck_altitude(65.0065) {
|
||||
FGAICarrier::FGAICarrier() :
|
||||
FGAIShip(otCarrier),
|
||||
deck_altitude(65.0065)
|
||||
{
|
||||
}
|
||||
|
||||
FGAICarrier::~FGAICarrier() {
|
||||
}
|
||||
FGAICarrier::~FGAICarrier() = default;
|
||||
|
||||
void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
|
||||
if (!scFileNode)
|
||||
|
@ -67,11 +67,14 @@ void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
|
|||
// Transform to the right coordinate frame, configuration is done in
|
||||
// the usual x-back, y-right, z-up coordinates, computations
|
||||
// in the simulation usual body x-forward, y-right, z-down coordinates
|
||||
flols_off(0) = - flols->getDoubleValue("x-offset-m", 0);
|
||||
flols_off(1) = flols->getDoubleValue("y-offset-m", 0);
|
||||
flols_off(2) = - flols->getDoubleValue("z-offset-m", 0);
|
||||
_flolsPosOffset(0) = - flols->getDoubleValue("x-offset-m", 0);
|
||||
_flolsPosOffset(1) = flols->getDoubleValue("y-offset-m", 0);
|
||||
_flolsPosOffset(2) = - flols->getDoubleValue("z-offset-m", 0);
|
||||
|
||||
_flolsHeadingOffsetDeg = flols->getDoubleValue("heading-offset-deg", 0.0);
|
||||
_flolsApproachAngle = flols->getDoubleValue("glidepath-angle-deg", 3.0);
|
||||
} else
|
||||
flols_off = SGVec3d::zeros();
|
||||
_flolsPosOffset = SGVec3d::zeros();
|
||||
|
||||
std::vector<SGPropertyNode_ptr> props = scFileNode->getChildren("parking-pos");
|
||||
std::vector<SGPropertyNode_ptr>::const_iterator it;
|
||||
|
@ -180,13 +183,13 @@ void FGAICarrier::update(double dt) {
|
|||
// rotate the eyepoint wrt carrier vector into the carriers frame
|
||||
eyeWrtCarrier = ec2body.transform(eyeWrtCarrier);
|
||||
// the eyepoints vector wrt the flols position
|
||||
SGVec3d eyeWrtFlols = eyeWrtCarrier - flols_off;
|
||||
SGVec3d eyeWrtFlols = eyeWrtCarrier - _flolsPosOffset;
|
||||
|
||||
// the distance from the eyepoint to the flols
|
||||
dist = norm(eyeWrtFlols);
|
||||
|
||||
// now the angle, positive angles are upwards
|
||||
if (fabs(dist) < SGLimits<float>::min()) {
|
||||
if (fabs(dist) < SGLimits<double>::min()) {
|
||||
angle = 0;
|
||||
} else {
|
||||
double sAngle = -eyeWrtFlols(2)/dist;
|
||||
|
@ -327,11 +330,9 @@ bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
|
|||
{
|
||||
|
||||
// FIXME: does not yet cover rotation speeds.
|
||||
list<ParkPosition>::iterator it = ppositions.begin();
|
||||
while (it != ppositions.end()) {
|
||||
for (const auto& ppos : ppositions) {
|
||||
// Take either the specified one or the first one ...
|
||||
if ((*it).name == id || id.empty()) {
|
||||
ParkPosition ppos = *it;
|
||||
if (ppos.name == id || id.empty()) {
|
||||
SGVec3d cartPos = getCartPosAt(ppos.offset);
|
||||
geodPos = SGGeod::fromCart(cartPos);
|
||||
hdng = hdg + ppos.heading_deg;
|
||||
|
@ -341,12 +342,28 @@ bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
|
|||
uvw = SGVec3d(chdng*speed_fps, shdng*speed_fps, 0);
|
||||
return true;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FGAICarrier::getFLOLSPositionHeading(SGGeod& geodPos, double& heading) const
|
||||
{
|
||||
SGVec3d cartPos = getCartPosAt(_flolsPosOffset);
|
||||
geodPos = SGGeod::fromCart(cartPos);
|
||||
|
||||
// at present we don't support a heading offset for the FLOLS, so
|
||||
// heading is just the carrier heading
|
||||
heading = hdg + _flolsHeadingOffsetDeg;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
double FGAICarrier::getFLOLFSGlidepathAngleDeg() const
|
||||
{
|
||||
return _flolsApproachAngle;
|
||||
}
|
||||
|
||||
// find relative wind
|
||||
void FGAICarrier::UpdateWind( double dt) {
|
||||
|
||||
|
@ -371,8 +388,7 @@ void FGAICarrier::UpdateWind( double dt) {
|
|||
+ (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts));
|
||||
|
||||
//calculate the relative wind direction
|
||||
rel_wind_from_deg = atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts)
|
||||
* SG_RADIANS_TO_DEGREES;
|
||||
rel_wind_from_deg = SGMiscd::rad2deg(atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts));
|
||||
|
||||
//calculate rel wind
|
||||
rel_wind = rel_wind_from_deg - hdg;
|
||||
|
@ -640,7 +656,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
|
|||
return {};
|
||||
}
|
||||
|
||||
for (const auto aiObject : aiManager->get_ai_list()) {
|
||||
for (const auto& aiObject : aiManager->get_ai_list()) {
|
||||
if (aiObject->isa(FGAIBase::otCarrier)) {
|
||||
SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get());
|
||||
if ((c->sign == namePennant) || (c->_getName() == namePennant)) {
|
||||
|
@ -652,7 +668,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
|
|||
return {};
|
||||
}
|
||||
|
||||
void FGAICarrier::extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario)
|
||||
void FGAICarrier::extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario)
|
||||
{
|
||||
for (auto c : xmlNode->getChildren("entry")) {
|
||||
if (c->getStringValue("type") != std::string("carrier"))
|
||||
|
@ -673,6 +689,14 @@ void FGAICarrier::extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, S
|
|||
// the find code above just looks for anything called a name (so alias
|
||||
// are possible, for example)
|
||||
if (!name.empty()) carrierNode->addChild("name")->setStringValue(name);
|
||||
if (!pennant.empty()) carrierNode->addChild("name")->setStringValue(pennant);
|
||||
if (!pennant.empty()) {
|
||||
carrierNode->addChild("name")->setStringValue(pennant);
|
||||
carrierNode->addChild("pennant-number")->setStringValue(pennant);
|
||||
}
|
||||
|
||||
// extact parkings
|
||||
for (auto p : c->getChildren("parking-pos")) {
|
||||
carrierNode->addChild("parking-pos")->setStringValue(p->getStringValue("name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,8 +86,11 @@ public:
|
|||
* This is used to support 'start on a carrier', since we can quickly find
|
||||
* the corresponding scenario file to be loaded.
|
||||
*/
|
||||
static void extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario);
|
||||
static void extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario);
|
||||
|
||||
bool getFLOLSPositionHeading(SGGeod &pos, double &heading) const;
|
||||
|
||||
double getFLOLFSGlidepathAngleDeg() const;
|
||||
private:
|
||||
/// Is sufficient to be private, stores a possible position to place an
|
||||
/// aircraft on start
|
||||
|
@ -115,8 +118,10 @@ private:
|
|||
list<ParkPosition> ppositions; // List of positions where an aircraft can start.
|
||||
string sign; // The sign of this carrier.
|
||||
|
||||
// these describe the flols
|
||||
SGVec3d flols_off;
|
||||
// these describe the flols
|
||||
SGVec3d _flolsPosOffset;
|
||||
double _flolsHeadingOffsetDeg = 0.0; ///< angle in degrees offset from the carrier centerline
|
||||
double _flolsApproachAngle = 3.0; ///< glidepath angle for the FLOLS
|
||||
|
||||
double dist; // the distance of the eyepoint from the flols
|
||||
double angle;
|
||||
|
|
|
@ -71,11 +71,11 @@ public:
|
|||
_unloadScript = nasalScripts->getStringValue("unload");
|
||||
std::string loadScript = nasalScripts->getStringValue("load");
|
||||
if (!loadScript.empty()) {
|
||||
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
|
||||
FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
|
||||
std::string moduleName = "scenario_" + _internalName;
|
||||
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
|
||||
loadScript.c_str(), loadScript.size(),
|
||||
0);
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ public:
|
|||
[](FGAIBasePtr ai) { ai->setDie(true); });
|
||||
|
||||
|
||||
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
|
||||
FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
|
||||
if (!nasalSys)
|
||||
return;
|
||||
|
||||
|
@ -93,7 +93,7 @@ public:
|
|||
if (!_unloadScript.empty()) {
|
||||
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
|
||||
_unloadScript.c_str(), _unloadScript.size(),
|
||||
0);
|
||||
nullptr);
|
||||
}
|
||||
|
||||
nasalSys->deleteModule(moduleName.c_str());
|
||||
|
@ -165,16 +165,19 @@ FGAIManager::init() {
|
|||
registerScenarios();
|
||||
}
|
||||
|
||||
void FGAIManager::registerScenarios()
|
||||
void FGAIManager::registerScenarios(SGPropertyNode_ptr root)
|
||||
{
|
||||
// depending on if we're using a carrier startup, this function may get
|
||||
// called early or during normal FGAIManager init, so guard against double
|
||||
// invocation.
|
||||
// we clear this flag on shudtdown so reset works as expected
|
||||
if (static_haveRegisteredScenarios)
|
||||
return;
|
||||
|
||||
static_haveRegisteredScenarios = true;
|
||||
if (!root) {
|
||||
// depending on if we're using a carrier startup, this function may get
|
||||
// called early or during normal FGAIManager init, so guard against double
|
||||
// invocation.
|
||||
// we clear this flag on shudtdown so reset works as expected
|
||||
if (static_haveRegisteredScenarios)
|
||||
return;
|
||||
|
||||
static_haveRegisteredScenarios = true;
|
||||
root = globals->get_props();
|
||||
}
|
||||
|
||||
// find all scenarios at standard locations (for driving the GUI)
|
||||
std::vector<SGPath> scenarioSearchPaths;
|
||||
|
@ -184,27 +187,29 @@ void FGAIManager::registerScenarios()
|
|||
|
||||
// add-on scenario directories
|
||||
const auto& addonsManager = flightgear::addons::AddonManager::instance();
|
||||
for (auto a : addonsManager->registeredAddons()) {
|
||||
scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios");
|
||||
if (addonsManager) {
|
||||
for (auto a : addonsManager->registeredAddons()) {
|
||||
scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios");
|
||||
}
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr scenariosNode = fgGetNode("/sim/ai/scenarios", true);
|
||||
|
||||
SGPropertyNode_ptr scenariosNode = root->getNode("/sim/ai/scenarios", true);
|
||||
for (auto p : scenarioSearchPaths) {
|
||||
if (!p.exists())
|
||||
continue;
|
||||
|
||||
simgear::Dir dir(p);
|
||||
for (auto xmlPath : dir.children(simgear::Dir::TYPE_FILE, ".xml")) {
|
||||
registerScenarioFile(xmlPath);
|
||||
registerScenarioFile(root, xmlPath);
|
||||
} // of xml files in the scenario dir iteration
|
||||
} // of scenario dirs iteration
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr FGAIManager::registerScenarioFile(const SGPath& xmlPath)
|
||||
SGPropertyNode_ptr FGAIManager::registerScenarioFile(SGPropertyNode_ptr root, const SGPath& xmlPath)
|
||||
{
|
||||
if (!xmlPath.exists()) return {};
|
||||
|
||||
auto scenariosNode = fgGetNode("/sim/ai/scenarios", true);
|
||||
auto scenariosNode = root->getNode("/sim/ai/scenarios", true);
|
||||
SGPropertyNode_ptr sNode;
|
||||
|
||||
try {
|
||||
|
@ -235,7 +240,7 @@ SGPropertyNode_ptr FGAIManager::registerScenarioFile(const SGPath& xmlPath)
|
|||
sNode->setStringValue("description", xs->getStringValue("description"));
|
||||
}
|
||||
|
||||
FGAICarrier::extractNamesPennantsFromScenario(xs, sNode);
|
||||
FGAICarrier::extractCarriersFromScenario(xs, sNode);
|
||||
} // of scenarios in the XML file
|
||||
} catch (std::exception&) {
|
||||
SG_LOG(SG_AI, SG_WARN, "Skipping malformed scenario file:" << xmlPath);
|
||||
|
@ -367,7 +372,7 @@ FGAIManager::update(double dt)
|
|||
for (FGAIBase* base : ai_list) {
|
||||
try {
|
||||
if (base->isa(FGAIBase::otThermal)) {
|
||||
processThermal(dt, (FGAIThermal*)base);
|
||||
processThermal(dt, static_cast<FGAIThermal*>(base));
|
||||
} else {
|
||||
base->update(dt);
|
||||
}
|
||||
|
@ -428,7 +433,7 @@ bool FGAIManager::isVisible(const SGGeod& pos) const
|
|||
int
|
||||
FGAIManager::getNumAiObjects() const
|
||||
{
|
||||
return ai_list.size();
|
||||
return static_cast<int>(ai_list.size());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -489,12 +494,14 @@ bool FGAIManager::loadScenarioCommand(const SGPropertyNode* args, SGPropertyNode
|
|||
|
||||
bool FGAIManager::unloadScenarioCommand(const SGPropertyNode * arg, SGPropertyNode * root)
|
||||
{
|
||||
SG_UNUSED(root);
|
||||
std::string name = arg->getStringValue("name");
|
||||
return unloadScenario(name);
|
||||
}
|
||||
|
||||
bool FGAIManager::addObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
|
||||
{
|
||||
SG_UNUSED(root);
|
||||
if (!arg){
|
||||
return false;
|
||||
}
|
||||
|
@ -506,7 +513,7 @@ FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
|
|||
{
|
||||
const std::string& type = definition->getStringValue("type", "aircraft");
|
||||
|
||||
FGAIBase* ai = NULL;
|
||||
FGAIBase* ai = nullptr;
|
||||
if (type == "tanker") { // refueling scenarios
|
||||
ai = new FGAITanker;
|
||||
} else if (type == "wingman") {
|
||||
|
@ -545,6 +552,7 @@ FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
|
|||
|
||||
bool FGAIManager::removeObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
|
||||
{
|
||||
SG_UNUSED(root);
|
||||
if (!arg) {
|
||||
return false;
|
||||
}
|
||||
|
@ -703,7 +711,7 @@ FGAIManager::calcCollision(double alt, double lat, double lon, double fuse_range
|
|||
}
|
||||
++ai_list_itr;
|
||||
}
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
double
|
||||
|
|
|
@ -73,8 +73,8 @@ public:
|
|||
* we need carrier scenarios to start the position-init process for a
|
||||
* carrier start.
|
||||
*/
|
||||
static void registerScenarios();
|
||||
static SGPropertyNode_ptr registerScenarioFile(const SGPath& p);
|
||||
static void registerScenarios(SGPropertyNode_ptr root = {});
|
||||
static SGPropertyNode_ptr registerScenarioFile(SGPropertyNode_ptr root, const SGPath& p);
|
||||
static SGPropertyNode_ptr loadScenarioFile(const std::string& id);
|
||||
|
||||
FGAIBasePtr addObject(const SGPropertyNode* definition);
|
||||
|
|
|
@ -126,6 +126,8 @@ if (HAVE_QT)
|
|||
PixmapImageItem.hxx
|
||||
PathListModel.cxx
|
||||
PathListModel.hxx
|
||||
CarriersLocationModel.cxx
|
||||
CarriersLocationModel.hxx
|
||||
${uic_sources}
|
||||
${qrc_sources}
|
||||
${qml_sources})
|
||||
|
|
132
src/GUI/CarriersLocationModel.cxx
Normal file
132
src/GUI/CarriersLocationModel.cxx
Normal file
|
@ -0,0 +1,132 @@
|
|||
#include "CarriersLocationModel.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
#include "AIModel/AIManager.hxx"
|
||||
|
||||
CarriersLocationModel::CarriersLocationModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
SGPropertyNode_ptr localRoot(new SGPropertyNode);
|
||||
FGAIManager::registerScenarios(localRoot);
|
||||
|
||||
// this code encodes some scenario structre, sorry
|
||||
for (auto s : localRoot->getNode("sim/ai/scenarios")->getChildren("scenario")) {
|
||||
const std::string scenarioId = s->getStringValue("id");
|
||||
for (auto c : s->getChildren("carrier")) {
|
||||
processCarrier(scenarioId, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CarriersLocationModel::processCarrier(const string &scenario, SGPropertyNode_ptr carrierNode)
|
||||
{
|
||||
const auto name = QString::fromStdString(carrierNode->getStringValue("name"));
|
||||
const auto pennant = QString::fromStdString(carrierNode->getStringValue("pennant-number"));
|
||||
const auto tacan = QString::fromStdString(carrierNode->getStringValue("TACAN-channel-ID"));
|
||||
SGGeod geod = SGGeod::fromDeg(carrierNode->getDoubleValue("longitude"),
|
||||
carrierNode->getDoubleValue("latitude"));
|
||||
|
||||
QStringList parkings;
|
||||
for (auto c : carrierNode->getChildren("parking-pos")) {
|
||||
parkings.append(QString::fromStdString(c->getStringValue()));
|
||||
}
|
||||
|
||||
mCarriers.push_back(Carrier{
|
||||
QString::fromStdString(scenario),
|
||||
pennant,
|
||||
name,
|
||||
geod,
|
||||
tacan,
|
||||
parkings
|
||||
});
|
||||
}
|
||||
|
||||
int CarriersLocationModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// For list models only the root node (an invalid parent) should return the list's size. For all
|
||||
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
|
||||
return static_cast<int>(mCarriers.size());
|
||||
}
|
||||
|
||||
QVariant CarriersLocationModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
const auto& c = mCarriers.at(static_cast<size_t>(index.row()));
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case NameRole: return c.mName;
|
||||
// case GeodRole: return QVariant::fromValue(c.mInitialLocation);
|
||||
case IdentRole: return c.mCallsign;
|
||||
case IconRole: return QPixmap(":/svg/aircraft-carrier");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CarriersLocationModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
||||
|
||||
result[GeodRole] = "geod";
|
||||
result[GuidRole] = "guid";
|
||||
result[IdentRole] = "ident";
|
||||
result[NameRole] = "name";
|
||||
result[IconRole] = "icon";
|
||||
result[TypeRole] = "type";
|
||||
result[NavFrequencyRole] = "frequency";
|
||||
return result;
|
||||
}
|
||||
|
||||
int CarriersLocationModel::indexOf(const QString name) const
|
||||
{
|
||||
auto it = std::find_if(mCarriers.begin(), mCarriers.end(), [name]
|
||||
(const Carrier& carrier)
|
||||
{ return name == carrier.mName || name == carrier.mCallsign; });
|
||||
if (it == mCarriers.end())
|
||||
return -1;
|
||||
|
||||
return static_cast<int>(std::distance(mCarriers.begin(), it));
|
||||
}
|
||||
|
||||
SGGeod CarriersLocationModel::geodForIndex(int index) const
|
||||
{
|
||||
const auto uIndex = static_cast<size_t>(index);
|
||||
if ((index < 0) || (uIndex >= mCarriers.size())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& c = mCarriers.at(uIndex);
|
||||
return c.mInitialLocation;
|
||||
}
|
||||
|
||||
QString CarriersLocationModel::pennantForIndex(int index) const
|
||||
{
|
||||
const auto uIndex = static_cast<size_t>(index);
|
||||
if ((index < 0) || (uIndex >= mCarriers.size())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& c = mCarriers.at(uIndex);
|
||||
return c.mCallsign;
|
||||
}
|
||||
|
||||
QStringList CarriersLocationModel::parkingsForIndex(int index) const
|
||||
{
|
||||
const auto uIndex = static_cast<size_t>(index);
|
||||
if ((index < 0) || (uIndex >= mCarriers.size())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& c = mCarriers.at(uIndex);
|
||||
return c.mParkings;
|
||||
}
|
61
src/GUI/CarriersLocationModel.hxx
Normal file
61
src/GUI/CarriersLocationModel.hxx
Normal file
|
@ -0,0 +1,61 @@
|
|||
#ifndef CARRIERSMODEL_H
|
||||
#define CARRIERSMODEL_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/math/sg_geodesy.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class CarriersLocationModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CarriersLocationModel(QObject *parent = nullptr);
|
||||
|
||||
// Basic functionality:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
// copied from NavaidSearchModel
|
||||
enum Roles {
|
||||
GeodRole = Qt::UserRole + 1,
|
||||
GuidRole = Qt::UserRole + 2,
|
||||
IdentRole = Qt::UserRole + 3,
|
||||
NameRole = Qt::UserRole + 4,
|
||||
IconRole = Qt::UserRole + 5,
|
||||
TypeRole = Qt::UserRole + 6,
|
||||
NavFrequencyRole = Qt::UserRole + 7
|
||||
};
|
||||
|
||||
int indexOf(const QString name) const;
|
||||
|
||||
SGGeod geodForIndex(int index) const;
|
||||
QString pennantForIndex(int index) const;
|
||||
|
||||
QStringList parkingsForIndex(int index) const;
|
||||
private:
|
||||
|
||||
struct Carrier
|
||||
{
|
||||
QString mScenario; // scenario ID for loading
|
||||
QString mCallsign; // pennant-number
|
||||
QString mName;
|
||||
SGGeod mInitialLocation;
|
||||
// icon?
|
||||
QString mTACAN;
|
||||
QStringList mParkings;
|
||||
};
|
||||
|
||||
using CarrierVec = std::vector<Carrier>;
|
||||
CarrierVec mCarriers;
|
||||
|
||||
void processCarrier(const std::string& scenario, SGPropertyNode_ptr carrierNode);
|
||||
};
|
||||
|
||||
#endif // CARRIERSMODEL_H
|
|
@ -52,6 +52,7 @@
|
|||
#include "NavaidSearchModel.hxx"
|
||||
#include "FlightPlanController.hxx"
|
||||
#include "ModelDataExtractor.hxx"
|
||||
#include "CarriersLocationModel.hxx"
|
||||
|
||||
using namespace simgear::pkg;
|
||||
|
||||
|
@ -142,6 +143,7 @@ void LauncherController::initQML()
|
|||
qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API");
|
||||
|
||||
qmlRegisterType<NavaidSearchModel>("FlightGear", 1, 0, "NavaidSearch");
|
||||
qmlRegisterType<CarriersLocationModel>("FlightGear", 1, 0, "CarriersModel");
|
||||
|
||||
qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum");
|
||||
qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "LaunchConfig.hxx"
|
||||
#include "DefaultAircraftLocator.hxx"
|
||||
#include "NavaidSearchModel.hxx"
|
||||
#include "CarriersLocationModel.hxx"
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Airports/groundnetwork.hxx>
|
||||
|
@ -53,10 +54,8 @@ const unsigned int MAX_RECENT_LOCATIONS = 64;
|
|||
QVariant savePositionList(const FGPositionedList& posList)
|
||||
{
|
||||
QVariantList vl;
|
||||
FGPositionedList::const_iterator it;
|
||||
for (it = posList.begin(); it != posList.end(); ++it) {
|
||||
for (const auto& pos : posList) {
|
||||
QVariantMap vm;
|
||||
FGPositionedRef pos = *it;
|
||||
vm.insert("ident", QString::fromStdString(pos->ident()));
|
||||
vm.insert("type", pos->type());
|
||||
vm.insert("lat", pos->geod().getLatitudeDeg());
|
||||
|
@ -70,7 +69,7 @@ FGPositionedList loadPositionedList(QVariant v)
|
|||
{
|
||||
QVariantList vl = v.toList();
|
||||
FGPositionedList result;
|
||||
result.reserve(vl.size());
|
||||
result.reserve(static_cast<size_t>(vl.size()));
|
||||
NavDataCache* cache = NavDataCache::instance();
|
||||
|
||||
Q_FOREACH(QVariant v, vl) {
|
||||
|
@ -93,9 +92,10 @@ FGPositionedList loadPositionedList(QVariant v)
|
|||
LocationController::LocationController(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
m_searchModel = new NavaidSearchModel;
|
||||
m_searchModel = new NavaidSearchModel(this);
|
||||
m_detailQml = new QmlPositioned(this);
|
||||
m_baseQml = new QmlPositioned(this);
|
||||
m_carriersModel = new CarriersLocationModel(this);
|
||||
|
||||
m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000};
|
||||
m_defaultAirspeed = QuantityValue{Units::Knots, 120};
|
||||
|
@ -112,9 +112,7 @@ LocationController::LocationController(QObject *parent) :
|
|||
this, &LocationController::descriptionChanged);
|
||||
}
|
||||
|
||||
LocationController::~LocationController()
|
||||
{
|
||||
}
|
||||
LocationController::~LocationController() = default;
|
||||
|
||||
void LocationController::setLaunchConfig(LaunchConfig *config)
|
||||
{
|
||||
|
@ -196,35 +194,62 @@ void LocationController::setBaseGeod(QmlGeod geod)
|
|||
if (m_locationIsLatLon && (m_geodLocation == geod.geod()))
|
||||
return;
|
||||
|
||||
clearLocation();
|
||||
m_locationIsLatLon = true;
|
||||
m_geodLocation = geod.geod();
|
||||
emit baseLocationChanged();
|
||||
}
|
||||
|
||||
QString LocationController::carrierName() const
|
||||
{
|
||||
return m_carrierName;
|
||||
}
|
||||
|
||||
void LocationController::setCarrierLocation(QString name)
|
||||
{
|
||||
const auto cIndex = m_carriersModel->indexOf(name);
|
||||
clearLocation();
|
||||
if (cIndex < 0) {
|
||||
qWarning() << "invalid carrier name:" << name;
|
||||
return;
|
||||
}
|
||||
|
||||
m_locationIsCarrier = true;
|
||||
m_carrierName = name;
|
||||
m_geodLocation = m_carriersModel->geodForIndex(cIndex);
|
||||
m_carrierParkings = m_carriersModel->parkingsForIndex(cIndex);
|
||||
|
||||
emit baseLocationChanged();
|
||||
}
|
||||
|
||||
void LocationController::clearLocation()
|
||||
{
|
||||
m_locationIsLatLon = false;
|
||||
m_locationIsCarrier = false;
|
||||
m_location.clear();
|
||||
m_carrierName.clear();
|
||||
m_airportLocation.clear();
|
||||
m_detailLocation.clear();
|
||||
m_detailQml->setGuid(0);
|
||||
m_baseQml->setGuid(0);
|
||||
m_carrierParkings.clear();
|
||||
m_carrierParking.clear();
|
||||
emit baseLocationChanged();
|
||||
}
|
||||
|
||||
void LocationController::setBaseLocation(QmlPositioned* pos)
|
||||
{
|
||||
if (!pos) {
|
||||
m_location.clear();
|
||||
m_detailLocation.clear();
|
||||
m_detailQml->setGuid(0);
|
||||
m_baseQml->setGuid(0);
|
||||
m_airportLocation.clear();
|
||||
m_locationIsLatLon = false;
|
||||
emit baseLocationChanged();
|
||||
clearLocation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (pos->inner() == m_location)
|
||||
return;
|
||||
|
||||
m_locationIsLatLon = false;
|
||||
clearLocation();
|
||||
m_location = pos->inner();
|
||||
m_baseQml->setGuid(pos->guid());
|
||||
m_detailLocation.clear();
|
||||
m_detailQml->setGuid(0);
|
||||
|
||||
if (FGPositioned::isAirportType(m_location.ptr())) {
|
||||
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
|
||||
|
@ -249,7 +274,6 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
|
|||
m_detailLocation.clear();
|
||||
m_detailQml->setInner({});
|
||||
} else {
|
||||
qInfo() << Q_FUNC_INFO << "pos:" << pos->ident();
|
||||
m_detailLocation = pos->inner();
|
||||
m_useActiveRunway = false;
|
||||
m_useAvailableParking = false;
|
||||
|
@ -261,7 +285,7 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
|
|||
|
||||
QmlGeod LocationController::baseGeod() const
|
||||
{
|
||||
if (m_locationIsLatLon)
|
||||
if (m_locationIsLatLon || m_locationIsCarrier)
|
||||
return m_geodLocation;
|
||||
|
||||
if (m_location)
|
||||
|
@ -368,6 +392,33 @@ QmlGeod LocationController::parseStringAsGeod(QString string) const
|
|||
return QmlGeod(g);
|
||||
}
|
||||
|
||||
QString LocationController::carrierParking() const
|
||||
{
|
||||
if (!m_locationIsCarrier)
|
||||
return {};
|
||||
return m_carrierParking;
|
||||
}
|
||||
|
||||
void LocationController::setCarrierParking(QString name)
|
||||
{
|
||||
if (!m_locationIsCarrier) {
|
||||
qWarning() << "active location is not a carrier";
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_carrierParking == name)
|
||||
return;
|
||||
|
||||
if (!m_carrierParkings.contains(name)) {
|
||||
qWarning() << "parking '" << name << "' not found in carrier parking list";
|
||||
return;
|
||||
}
|
||||
|
||||
m_carrierParking = name;
|
||||
m_useCarrierFLOLS = false;
|
||||
emit configChanged();
|
||||
}
|
||||
|
||||
QmlPositioned *LocationController::detail() const
|
||||
{
|
||||
return m_detailQml;
|
||||
|
@ -378,6 +429,11 @@ QmlPositioned *LocationController::baseLocation() const
|
|||
return m_baseQml;
|
||||
}
|
||||
|
||||
QStringList LocationController::carrierParkings() const
|
||||
{
|
||||
return m_carrierParkings;
|
||||
}
|
||||
|
||||
void LocationController::setOffsetRadial(QuantityValue offsetRadial)
|
||||
{
|
||||
if (m_offsetRadial == offsetRadial)
|
||||
|
@ -436,27 +492,26 @@ void LocationController::setUseAvailableParking(bool useAvailableParking)
|
|||
emit configChanged();
|
||||
}
|
||||
|
||||
void LocationController::setUseCarrierFLOLS(bool useCarrierFLOLS)
|
||||
{
|
||||
if (!m_locationIsCarrier) {
|
||||
qWarning() << "location is not a carrier";
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_useCarrierFLOLS == useCarrierFLOLS)
|
||||
return;
|
||||
|
||||
m_useCarrierFLOLS = useCarrierFLOLS;
|
||||
m_carrierParking.clear();
|
||||
emit configChanged();
|
||||
}
|
||||
|
||||
void LocationController::restoreLocation(QVariantMap l)
|
||||
{
|
||||
try {
|
||||
if (l.contains("location-lat")) {
|
||||
m_locationIsLatLon = true;
|
||||
m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
|
||||
l.value("location-lat").toDouble());
|
||||
m_location.clear();
|
||||
m_airportLocation.clear();
|
||||
m_baseQml->setInner(nullptr);
|
||||
} else if (l.contains("location-id")) {
|
||||
m_location = NavDataCache::instance()->loadById(l.value("location-id").toULongLong());
|
||||
m_locationIsLatLon = false;
|
||||
if (FGPositioned::isAirportType(m_location.ptr())) {
|
||||
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
|
||||
} else {
|
||||
m_airportLocation.clear();
|
||||
}
|
||||
m_baseQml->setInner(m_location);
|
||||
}
|
||||
clearLocation();
|
||||
|
||||
try {
|
||||
m_altitudeEnabled = l.contains("altitude");
|
||||
m_speedEnabled = l.contains("speed");
|
||||
m_headingEnabled = l.contains("heading");
|
||||
|
@ -470,10 +525,33 @@ void LocationController::restoreLocation(QVariantMap l)
|
|||
m_offsetDistance = l.value("offset-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
|
||||
m_tuneNAV1 = l.value("tune-nav1-radio").toBool();
|
||||
|
||||
if (l.contains("location-lat")) {
|
||||
m_locationIsLatLon = true;
|
||||
m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
|
||||
l.value("location-lat").toDouble());
|
||||
} else if (l.contains("carrier")) {
|
||||
setCarrierLocation(l.value("carrier").toString());
|
||||
if (l.contains("carrier-flols")) {
|
||||
setUseCarrierFLOLS(l.value("carrier-flols").toBool());
|
||||
// overwrite value form above, intentionally
|
||||
m_offsetDistance = l.value("location-carrier-flols-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
|
||||
} else if (l.contains("carrier-parking")) {
|
||||
setCarrierParking(l.value("carrier-parking").toString());
|
||||
}
|
||||
} else if (l.contains("location-id")) {
|
||||
m_location = NavDataCache::instance()->loadById(l.value("location-id").toLongLong());
|
||||
m_locationIsLatLon = false;
|
||||
if (FGPositioned::isAirportType(m_location.ptr())) {
|
||||
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
|
||||
} else {
|
||||
m_airportLocation.clear();
|
||||
}
|
||||
m_baseQml->setInner(m_location);
|
||||
}
|
||||
|
||||
if (m_airportLocation) {
|
||||
m_useActiveRunway = false;
|
||||
m_useAvailableParking = false;
|
||||
m_detailLocation.clear();
|
||||
|
||||
if (l.contains("location-apt-runway")) {
|
||||
QString runway = l.value("location-apt-runway").toString().toUpper();
|
||||
|
@ -502,10 +580,7 @@ void LocationController::restoreLocation(QVariantMap l)
|
|||
} // of location is an airport
|
||||
} catch (const sg_exception&) {
|
||||
qWarning() << "Errors restoring saved location, clearing";
|
||||
m_location.clear();
|
||||
m_airportLocation.clear();
|
||||
m_baseQml->setInner(nullptr);
|
||||
m_offsetEnabled = false;
|
||||
clearLocation();
|
||||
}
|
||||
|
||||
baseLocationChanged();
|
||||
|
@ -515,6 +590,10 @@ void LocationController::restoreLocation(QVariantMap l)
|
|||
|
||||
bool LocationController::shouldStartPaused() const
|
||||
{
|
||||
if (m_useCarrierFLOLS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_location) {
|
||||
return false; // defaults to on-ground at the default airport
|
||||
}
|
||||
|
@ -535,6 +614,14 @@ QVariantMap LocationController::saveLocation() const
|
|||
if (m_locationIsLatLon) {
|
||||
locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg());
|
||||
locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg());
|
||||
} else if (m_locationIsCarrier) {
|
||||
locationSet.insert("carrier", m_carrierName);
|
||||
if (m_useCarrierFLOLS) {
|
||||
locationSet.insert("carrier-flols", true);
|
||||
locationSet.insert("location-carrier-flols-distance", QVariant::fromValue(m_offsetDistance));
|
||||
} else if (!m_carrierParking.isEmpty()) {
|
||||
locationSet.insert("carrier-parking", m_carrierParking);
|
||||
}
|
||||
} else if (m_location) {
|
||||
locationSet.insert("location-id", static_cast<qlonglong>(m_location->guid()));
|
||||
|
||||
|
@ -588,7 +675,7 @@ void LocationController::setLocationProperties()
|
|||
"runway-requested" << "navaid-id" << "offset-azimuth-deg" <<
|
||||
"offset-distance-nm" << "glideslope-deg" <<
|
||||
"speed-set" << "on-ground" << "airspeed-kt" <<
|
||||
"airport-id" << "runway" << "parkpos";
|
||||
"airport-id" << "runway" << "parkpos" << "carrier";
|
||||
|
||||
Q_FOREACH(QString s, props) {
|
||||
SGPropertyNode* c = presets->getChild(s.toStdString());
|
||||
|
@ -604,6 +691,8 @@ void LocationController::setLocationProperties()
|
|||
fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
|
||||
|
||||
applyPositionOffset();
|
||||
applyAltitude();
|
||||
applyAirspeed();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -612,6 +701,27 @@ void LocationController::setLocationProperties()
|
|||
fgSetDouble("/sim/presets/altitude-ft", -9999.0);
|
||||
fgSetDouble("/sim/presets/heading-deg", 9999.0);
|
||||
|
||||
if (m_locationIsCarrier) {
|
||||
fgSetString("/sim/presets/carrier", m_carrierName.toStdString());
|
||||
|
||||
if (m_useCarrierFLOLS) {
|
||||
fgSetBool("/sim/presets/runway-requested", true );
|
||||
// treat the FLOLS as a runway, for the purposes of communication with position-init
|
||||
fgSetString("/sim/presets/runway", "FLOLS");
|
||||
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
|
||||
|
||||
applyAirspeed();
|
||||
} else if (!m_carrierParking.isEmpty()) {
|
||||
fgSetString("/sim/presets/parkpos", m_carrierParking.toStdString());
|
||||
}
|
||||
|
||||
if (m_tuneNAV1) {
|
||||
// tune TACAN to the carrier
|
||||
qInfo() << "Implement TACAN tuning";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_location) {
|
||||
return;
|
||||
}
|
||||
|
@ -681,7 +791,7 @@ void LocationController::setLocationProperties()
|
|||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
// set disambiguation property
|
||||
globals->get_props()->setIntValue("/sim/presets/navaid-id",
|
||||
|
@ -796,6 +906,21 @@ void LocationController::onCollectConfig()
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_locationIsCarrier) {
|
||||
m_config->setArg("carrier", m_carrierName);
|
||||
|
||||
if (!m_carrierParking.isEmpty()) {
|
||||
m_config->setArg("parkpos", m_carrierParking);
|
||||
} else if (m_useCarrierFLOLS) {
|
||||
m_config->setArg("runway", QStringLiteral("FLOLS"));
|
||||
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
|
||||
m_config->setArg("offset-distance", QString::number(offsetNm));
|
||||
applyAirspeed();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_location) {
|
||||
return;
|
||||
}
|
||||
|
@ -852,7 +977,7 @@ void LocationController::onCollectConfig()
|
|||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
// set disambiguation property
|
||||
m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid()));
|
||||
|
@ -912,6 +1037,11 @@ QString LocationController::description() const
|
|||
return tr("at position %1").arg(QString::fromStdString(s));
|
||||
}
|
||||
|
||||
if (m_locationIsCarrier) {
|
||||
QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName));
|
||||
return tr("on carrier %1 (%2)").arg(m_carrierName).arg(pennant);
|
||||
}
|
||||
|
||||
return tr("No location selected");
|
||||
}
|
||||
|
||||
|
@ -952,7 +1082,7 @@ QString LocationController::description() const
|
|||
if (m_offsetEnabled) {
|
||||
offsetDesc = tr("%1nm %2 of").
|
||||
arg(offsetNm, 0, 'f', 1).
|
||||
arg(compassPointFromHeading(m_offsetRadial.value));
|
||||
arg(compassPointFromHeading(static_cast<int>(m_offsetRadial.value)));
|
||||
}
|
||||
|
||||
QString navaidType;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "UnitsModel.hxx"
|
||||
|
||||
class NavaidSearchModel;
|
||||
class CarriersLocationModel;
|
||||
|
||||
class LocationController : public QObject
|
||||
{
|
||||
|
@ -40,6 +41,7 @@ class LocationController : public QObject
|
|||
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
|
||||
|
||||
Q_PROPERTY(NavaidSearchModel* searchModel MEMBER m_searchModel CONSTANT)
|
||||
Q_PROPERTY(CarriersLocationModel* carriersModel MEMBER m_carriersModel CONSTANT)
|
||||
|
||||
Q_PROPERTY(QList<QObject*> airportRunways READ airportRunways NOTIFY baseLocationChanged)
|
||||
Q_PROPERTY(QList<QObject*> airportParkings READ airportParkings NOTIFY baseLocationChanged)
|
||||
|
@ -68,6 +70,13 @@ class LocationController : public QObject
|
|||
Q_PROPERTY(QmlPositioned* detail READ detail CONSTANT)
|
||||
Q_PROPERTY(bool isBaseLatLon READ isBaseLatLon NOTIFY baseLocationChanged)
|
||||
|
||||
Q_PROPERTY(bool isCarrier READ isCarrier NOTIFY baseLocationChanged)
|
||||
Q_PROPERTY(QString carrier READ carrierName WRITE setCarrierLocation NOTIFY baseLocationChanged)
|
||||
Q_PROPERTY(QStringList carrierParkings READ carrierParkings NOTIFY baseLocationChanged)
|
||||
Q_PROPERTY(bool useCarrierFLOLS READ useCarrierFLOLS WRITE setUseCarrierFLOLS NOTIFY configChanged)
|
||||
Q_PROPERTY(QString carrierParking READ carrierParking WRITE setCarrierParking NOTIFY configChanged)
|
||||
|
||||
|
||||
// allow collecting the location properties to be disabled, if the
|
||||
// user is setting conflicting ones
|
||||
Q_PROPERTY(bool skipFromArgs MEMBER m_skipFromArgs NOTIFY skipFromArgsChanged)
|
||||
|
@ -110,6 +119,9 @@ public:
|
|||
QmlGeod baseGeod() const;
|
||||
void setBaseGeod(QmlGeod geod);
|
||||
|
||||
QString carrierName() const;
|
||||
void setCarrierLocation(QString name);
|
||||
|
||||
bool isAirportLocation() const;
|
||||
|
||||
bool offsetEnabled() const
|
||||
|
@ -138,6 +150,9 @@ public:
|
|||
|
||||
Q_INVOKABLE QmlGeod parseStringAsGeod(QString string) const;
|
||||
|
||||
QString carrierParking() const;
|
||||
void setCarrierParking(QString name);
|
||||
|
||||
bool tuneNAV1() const
|
||||
{
|
||||
return m_tuneNAV1;
|
||||
|
@ -161,6 +176,19 @@ public:
|
|||
{
|
||||
return m_altitude;
|
||||
}
|
||||
|
||||
bool isCarrier() const
|
||||
{
|
||||
return m_locationIsCarrier;
|
||||
}
|
||||
|
||||
QStringList carrierParkings() const;
|
||||
|
||||
bool useCarrierFLOLS() const
|
||||
{
|
||||
return m_useCarrierFLOLS;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void setOffsetRadial(QuantityValue offsetRadial);
|
||||
|
||||
|
@ -174,6 +202,8 @@ public slots:
|
|||
|
||||
void setUseAvailableParking(bool useAvailableParking);
|
||||
|
||||
void setUseCarrierFLOLS(bool useCarrierFLOLS);
|
||||
|
||||
Q_SIGNALS:
|
||||
void descriptionChanged();
|
||||
void offsetChanged();
|
||||
|
@ -186,6 +216,7 @@ private Q_SLOTS:
|
|||
void onRestoreCurrentLocation();
|
||||
void onSaveCurrentLocation();
|
||||
private:
|
||||
void clearLocation();
|
||||
|
||||
void onSearchComplete();
|
||||
void addToRecent(FGPositionedRef pos);
|
||||
|
@ -197,12 +228,15 @@ private:
|
|||
void applyOnFinal();
|
||||
|
||||
NavaidSearchModel* m_searchModel = nullptr;
|
||||
CarriersLocationModel* m_carriersModel = nullptr;
|
||||
|
||||
FGPositionedRef m_location;
|
||||
FGAirportRef m_airportLocation; // valid if m_location is an FGAirport
|
||||
FGPositionedRef m_detailLocation; // parking stand or runway detail
|
||||
bool m_locationIsLatLon = false;
|
||||
SGGeod m_geodLocation;
|
||||
bool m_locationIsCarrier = false;
|
||||
QString m_carrierName;
|
||||
|
||||
FGPositionedList m_recentLocations;
|
||||
LaunchConfig* m_config = nullptr;
|
||||
|
@ -227,6 +261,10 @@ private:
|
|||
bool m_speedEnabled = false;
|
||||
bool m_altitudeEnabled = false;
|
||||
bool m_skipFromArgs = false;
|
||||
|
||||
bool m_useCarrierFLOLS = false;
|
||||
QString m_carrierParking;
|
||||
QStringList m_carrierParkings;
|
||||
};
|
||||
|
||||
#endif // LOCATION_CONTROLLER_HXX
|
||||
|
|
|
@ -24,35 +24,49 @@ QVariant ModelDataExtractor::data() const
|
|||
return m_model->data(m, role);
|
||||
}
|
||||
|
||||
if (m_value.isArray()) {
|
||||
if (!m_stringsModel.empty()) {
|
||||
if ((m_index < 0) || (m_index >= m_stringsModel.size()))
|
||||
return {};
|
||||
|
||||
return m_stringsModel.at(m_index);
|
||||
}
|
||||
|
||||
if (m_rawModel.isArray()) {
|
||||
quint32 uIndex = static_cast<quint32>(m_index);
|
||||
auto v = m_value.property(uIndex);
|
||||
auto v = m_rawModel.property(uIndex);
|
||||
if (v.isQObject()) {
|
||||
// handle the QList<QObject*> case
|
||||
auto obj = v.toQObject();
|
||||
return obj->property(m_role.toUtf8().constData());
|
||||
}
|
||||
|
||||
return m_value.property(uIndex).toVariant();
|
||||
return m_rawModel.property(uIndex).toVariant();
|
||||
}
|
||||
|
||||
qWarning() << "Unable to convert model data:" << m_rawModel.toString();
|
||||
return {};
|
||||
}
|
||||
|
||||
void ModelDataExtractor::setModel(QJSValue model)
|
||||
void ModelDataExtractor::clear()
|
||||
{
|
||||
if (m_value.equals(model))
|
||||
return;
|
||||
|
||||
if (m_model) {
|
||||
// disconnect from everything
|
||||
disconnect(m_model, nullptr, this, nullptr);
|
||||
m_model = nullptr;
|
||||
}
|
||||
|
||||
m_value = model;
|
||||
if (m_value.isQObject()) {
|
||||
m_model = qobject_cast<QAbstractItemModel*>(m_value.toQObject());
|
||||
}
|
||||
|
||||
void ModelDataExtractor::setModel(QJSValue raw)
|
||||
{
|
||||
if (m_rawModel.strictlyEquals(raw))
|
||||
return;
|
||||
|
||||
clear();
|
||||
m_rawModel = raw;
|
||||
|
||||
if (raw.isQObject()) {
|
||||
m_model = qobject_cast<QAbstractItemModel*>(raw.toQObject());
|
||||
if (m_model) {
|
||||
connect(m_model, &QAbstractItemModel::modelReset,
|
||||
this, &ModelDataExtractor::dataChanged);
|
||||
|
@ -61,10 +75,24 @@ void ModelDataExtractor::setModel(QJSValue model)
|
|||
|
||||
// ToDo: handle rows added / removed
|
||||
} else {
|
||||
qWarning() << "object but not a QAIM" << m_value.toQObject();
|
||||
qWarning() << "object but not a QAIM" << raw.toQObject();
|
||||
}
|
||||
} else if (raw.isArray()) {
|
||||
|
||||
} else if (raw.isVariant() || raw.isObject()) {
|
||||
// special case the QStringList case
|
||||
|
||||
// for reasons I don't understand yet, QStringList returned as a
|
||||
// property value to JS, does not show up as a variant-in-JS-Value above
|
||||
// (so ::isVariant returns false), but conversion to a variant
|
||||
// works. Hence the 'raw.isObject' above
|
||||
|
||||
const auto var = raw.toVariant();
|
||||
if (var.type() == QVariant::StringList) {
|
||||
m_stringsModel = var.toStringList();
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "variant but not a QStringList" << var;
|
||||
}
|
||||
} else {
|
||||
// might be null, or an array
|
||||
}
|
||||
|
||||
emit modelChanged();
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
|
||||
QJSValue model() const
|
||||
{
|
||||
return m_value;
|
||||
return m_rawModel;
|
||||
}
|
||||
|
||||
int index() const
|
||||
|
@ -52,8 +52,12 @@ private slots:
|
|||
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
private:
|
||||
void clear();
|
||||
|
||||
QAbstractItemModel* m_model = nullptr;
|
||||
QJSValue m_value;
|
||||
QJSValue m_rawModel;
|
||||
QStringList m_stringsModel;
|
||||
|
||||
int m_index = 0;
|
||||
QString m_role;
|
||||
};
|
||||
|
|
|
@ -153,10 +153,17 @@ void NavaidSearchModel::clear()
|
|||
|
||||
qlonglong NavaidSearchModel::guidAtIndex(int index) const
|
||||
{
|
||||
if ((index < 0) || (index >= m_ids.size()))
|
||||
const size_t uIndex = static_cast<size_t>(index);
|
||||
if ((index < 0) || (uIndex >= m_ids.size()))
|
||||
return 0;
|
||||
|
||||
return m_ids.at(index);
|
||||
return m_ids.at(uIndex);
|
||||
}
|
||||
|
||||
NavaidSearchModel::NavaidSearchModel(QObject *parent) :
|
||||
QAbstractListModel(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void NavaidSearchModel::setSearch(QString t, NavaidSearchModel::AircraftType aircraft)
|
||||
|
|
|
@ -51,7 +51,7 @@ class NavaidSearchModel : public QAbstractListModel
|
|||
};
|
||||
|
||||
public:
|
||||
NavaidSearchModel() { }
|
||||
NavaidSearchModel(QObject* parent = nullptr);
|
||||
|
||||
enum AircraftType
|
||||
{
|
||||
|
|
BIN
src/GUI/assets/aircraft-carrier-icon.png
Normal file
BIN
src/GUI/assets/aircraft-carrier-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
src/GUI/assets/icons8-cargo-ship-50.png
Normal file
BIN
src/GUI/assets/icons8-cargo-ship-50.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 890 B |
30
src/GUI/qml/IconButton.qml
Normal file
30
src/GUI/qml/IconButton.qml
Normal file
|
@ -0,0 +1,30 @@
|
|||
import QtQuick 2.0
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
radius: Style.roundRadius
|
||||
border.width: 1
|
||||
border.color: Style.themeColor
|
||||
width: height
|
||||
height: Style.baseFontPixelSize + Style.margin * 2
|
||||
color: mouse.containsMouse ? Style.minorFrameColor : "white"
|
||||
|
||||
property alias icon: icon.source
|
||||
|
||||
signal clicked();
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
width: parent.width - Style.margin
|
||||
height: parent.height - Style.margin
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
hoverEnabled: true
|
||||
onClicked: root.clicked();
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
|
@ -9,6 +9,9 @@ Item {
|
|||
property bool __searchActive: false
|
||||
property string lastSearch
|
||||
|
||||
property bool showCarriers: false
|
||||
readonly property var locationModel: showCarriers ? _location.carriersModel : _location.searchModel
|
||||
|
||||
function backToSearch()
|
||||
{
|
||||
detailLoader.sourceComponent = null
|
||||
|
@ -33,6 +36,13 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
function selectCarrier(name)
|
||||
{
|
||||
selectedLocation.guid = 0;
|
||||
_location.carrier = name;
|
||||
detailLoader.sourceComponent = carrierDetails
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// important so we can leave the location page and return to it,
|
||||
// preserving the state
|
||||
|
@ -46,6 +56,8 @@ Item {
|
|||
}
|
||||
} else if (_location.isBaseLatLon) {
|
||||
detailLoader.sourceComponent = navaidDetails
|
||||
} else if (_location.isCarrier) {
|
||||
detailLoader.sourceComponent = carrierDetails;
|
||||
} else {
|
||||
_location.showHistoryInSearchModel();
|
||||
}
|
||||
|
@ -69,6 +81,13 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: carrierDetails
|
||||
LocationCarrierView {
|
||||
id: carrierView
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "white"
|
||||
|
@ -129,7 +148,11 @@ Item {
|
|||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
root.selectLocation(model.guid, model.type);
|
||||
if (root.showCarriers) {
|
||||
root.selectCarrier(model.name);
|
||||
} else {
|
||||
root.selectLocation(model.guid, model.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,10 +183,12 @@ Item {
|
|||
anchors.topMargin: Style.margin
|
||||
}
|
||||
|
||||
|
||||
|
||||
SearchButton {
|
||||
id: searchButton
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.right: carriersButton.left
|
||||
anchors.top: headerText.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Style.margin
|
||||
|
@ -172,7 +197,8 @@ Item {
|
|||
placeholder: qsTr("Search for an airport or navaid");
|
||||
|
||||
onSearch: {
|
||||
// when th search term is cleared, show the history
|
||||
root.showCarriers = false;
|
||||
// when the search term is cleared, show the history
|
||||
if (term == "") {
|
||||
_location.showHistoryInSearchModel();
|
||||
return;
|
||||
|
@ -191,6 +217,18 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: carriersButton
|
||||
anchors.top: headerText.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.margin
|
||||
icon: "qrc:///svg/icon-carrier"
|
||||
|
||||
onClicked: {
|
||||
root.showCarriers = true;
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: searchHelpText
|
||||
anchors.right: parent.right
|
||||
|
@ -220,12 +258,12 @@ Item {
|
|||
anchors.topMargin: Style.margin
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
model: _location.searchModel
|
||||
model: root.locationModel
|
||||
delegate: locationSearchDelegate
|
||||
clip: true
|
||||
|
||||
header: Item {
|
||||
visible: _location.searchModel.isSearchActive
|
||||
visible: !root.showCarriers && _location.searchModel.isSearchActive
|
||||
width: parent.width
|
||||
height: visible ? 50 : 0
|
||||
|
||||
|
@ -246,7 +284,7 @@ Item {
|
|||
footer: Item {
|
||||
width: parent.width
|
||||
height: noResultsText.height
|
||||
visible: (parent.count === 0) && !_location.searchModel.isSearchActive
|
||||
visible: !root.showCarriers && (parent.count === 0) && !_location.searchModel.isSearchActive
|
||||
Text {
|
||||
id: noResultsText
|
||||
width: parent.width
|
||||
|
|
214
src/GUI/qml/LocationCarrierView.qml
Normal file
214
src/GUI/qml/LocationCarrierView.qml
Normal file
|
@ -0,0 +1,214 @@
|
|||
import QtQuick 2.4
|
||||
import FlightGear 1.0
|
||||
import FlightGear.Launcher 1.0
|
||||
import "."
|
||||
|
||||
Item {
|
||||
property alias geod: diagram.geod
|
||||
|
||||
NavaidDiagram {
|
||||
id: diagram
|
||||
anchors.fill: parent
|
||||
|
||||
offsetEnabled: _location.offsetEnabled
|
||||
offsetBearing: _location.offsetRadial
|
||||
offsetDistance: _location.offsetDistance
|
||||
heading: _location.heading
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
syncUIFromController();
|
||||
}
|
||||
|
||||
function syncUIFromController()
|
||||
{
|
||||
if (_location.useCarrierFLOLS) {
|
||||
flolsRadio.select()
|
||||
} else {
|
||||
parkingRadio.select();
|
||||
parkingChoice.syncCurrentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: panel
|
||||
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Style.frameColor
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
margins: Style.strutSize
|
||||
}
|
||||
|
||||
height: selectionGrid.height + Style.margin * 2
|
||||
|
||||
// set opacity here only, so we don't make the whole summary pannel translucent
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
opacity: Style.panelOpacity
|
||||
color: "white"
|
||||
}
|
||||
|
||||
RadioButtonGroup {
|
||||
id: radioGroup
|
||||
}
|
||||
|
||||
Column {
|
||||
id: selectionGrid
|
||||
spacing: Style.margin
|
||||
width: parent.width
|
||||
|
||||
StyledText { // heading text
|
||||
id: headingText
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: Style.margin
|
||||
}
|
||||
|
||||
text: qsTr("Carrier: %1").arg(_location.carrier);
|
||||
font.pixelSize: Style.headingFontPixelSize
|
||||
}
|
||||
|
||||
// on FLOLS offset
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.margin
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.margin
|
||||
spacing: Style.margin
|
||||
|
||||
RadioButton {
|
||||
id: flolsRadio
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
group: radioGroup
|
||||
onClicked: {
|
||||
if (selected) _location.useCarrierFLOLS = selected
|
||||
}
|
||||
selected: _location.useCarrierFLOLS
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("On final approach")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
enabled: flolsRadio.selected
|
||||
}
|
||||
|
||||
|
||||
NumericalEdit {
|
||||
id: offsetNmEdit
|
||||
quantity: _location.offsetDistance
|
||||
onCommit: _location.offsetDistance = newValue;
|
||||
label: qsTr("at")
|
||||
unitsMode: Units.Distance
|
||||
live: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
enabled: flolsRadio.selected
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr(" from the FLOLS (aka the ball)")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
enabled: flolsRadio.selected
|
||||
}
|
||||
|
||||
Item {
|
||||
height: 1; width: Style.strutSize
|
||||
}
|
||||
|
||||
ToggleSwitch {
|
||||
id: airspeedToggle
|
||||
enabled: flolsRadio.selected
|
||||
checked: _location.speedEnabled
|
||||
onCheckedChanged: _location.speedEnabled = checked;
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NumericalEdit {
|
||||
id: airspeedSpinbox
|
||||
label: qsTr("Airspeed:")
|
||||
unitsMode: Units.SpeedWithoutMach
|
||||
enabled: _location.speedEnabled && flolsRadio.selected
|
||||
quantity: _location.airspeed
|
||||
onCommit: _location.airspeed = newValue
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
} // of FLOLS row
|
||||
|
||||
// parking row
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.margin
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.margin
|
||||
spacing: Style.margin
|
||||
|
||||
// hide if there's no parking locations defined for this carrier
|
||||
visible: _location.carrierParkings.length > 0
|
||||
|
||||
RadioButton {
|
||||
id: parkingRadio
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
group: radioGroup
|
||||
onClicked: {
|
||||
if (selected) parkingChoice.setLocation();
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Parking")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
enabled: parkingRadio.selected
|
||||
}
|
||||
|
||||
PopupChoice {
|
||||
id: parkingChoice
|
||||
model: _location.carrierParkings
|
||||
// displayRole: "modelData"
|
||||
width: parent.width * 0.5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
enabled: parkingRadio.selected
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
setLocation();
|
||||
}
|
||||
|
||||
function syncCurrentIndex()
|
||||
{
|
||||
for (var i=0; i < _location.carrierParkings.length; ++i) {
|
||||
if (_location.carrierParkings[i] === _location.carrierParking) {
|
||||
currentIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// not found, default to available
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
function setLocation()
|
||||
{
|
||||
_location.carrierParking = _location.carrierParkings[currentIndex]
|
||||
//diagram.selection = _location.airportParkings[currentIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToggleSwitch {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.margin
|
||||
label: qsTr("Tune navigation radio (TACAN) to carrier")
|
||||
checked: _location.tuneNAV1
|
||||
onCheckedChanged: {
|
||||
_location.tuneNAV1 = checked
|
||||
}
|
||||
}
|
||||
} // main layout column
|
||||
} // main panel rectangle
|
||||
}
|
|
@ -91,6 +91,7 @@
|
|||
<file>qml/LineEdit.qml</file>
|
||||
<file>qml/LocationAirportView.qml</file>
|
||||
<file>qml/LocationNavaidView.qml</file>
|
||||
<file>qml/LocationCarrierView.qml</file>
|
||||
<file alias="linear-spinner">qml/icons8-linear-spinner.gif</file>
|
||||
<file alias="ellipsis-icon">qml/icons8-ellipsis-filled-50.png</file>
|
||||
<file>qml/RadioButton.qml</file>
|
||||
|
@ -130,6 +131,7 @@
|
|||
<file>qml/AircraftListView.qml</file>
|
||||
<file>qml/GridToggleButton.qml</file>
|
||||
<file>qml/EnableDisableButton.qml</file>
|
||||
<file>qml/IconButton.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/preview">
|
||||
<file alias="close-icon">preview-close.png</file>
|
||||
|
@ -149,5 +151,7 @@
|
|||
<file alias="icon-grid-view">assets/icons8-grid-view.svg</file>
|
||||
<file alias="icon-list-view">assets/icons8-menu.svg</file>
|
||||
<file alias="icon-hide">assets/icons8-hide-50.png</file>
|
||||
<file alias="icon-carrier">assets/icons8-cargo-ship-50.png</file>
|
||||
<file alias="aircraft-carrier">assets/aircraft-carrier-icon.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -1423,7 +1423,7 @@ fgOptScenario( const char *arg )
|
|||
}
|
||||
|
||||
// create description node
|
||||
auto n = FGAIManager::registerScenarioFile(path);
|
||||
auto n = FGAIManager::registerScenarioFile(globals->get_props(), path);
|
||||
if (!n) {
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "failed to read scenario file at:" << path);
|
||||
return FG_OPTIONS_ERROR;
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
using std::endl;
|
||||
using std::string;
|
||||
|
||||
|
||||
namespace flightgear
|
||||
{
|
||||
|
||||
|
@ -68,8 +69,11 @@ static bool global_callbackRegistered = false;
|
|||
|
||||
static void finalizePosition();
|
||||
|
||||
namespace { // annonymous namepsace to avoid warnings about inline classes
|
||||
|
||||
// Set current tower position lon/lat given an airport id
|
||||
static bool fgSetTowerPosFromAirportID( const string& id) {
|
||||
bool fgSetTowerPosFromAirportID( const string& id)
|
||||
{
|
||||
const FGAirport *a = fgFindAirportID( id);
|
||||
if (a) {
|
||||
SGGeod tower = a->getTowerLocation();
|
||||
|
@ -80,12 +84,12 @@ static bool fgSetTowerPosFromAirportID( const string& id) {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FGTowerLocationListener : public SGPropertyChangeListener {
|
||||
|
||||
void valueChanged(SGPropertyNode* node) {
|
||||
void valueChanged(SGPropertyNode* node) override
|
||||
{
|
||||
string id(node->getStringValue());
|
||||
if (fgGetBool("/sim/tower/auto-position",true))
|
||||
{
|
||||
|
@ -116,6 +120,9 @@ class FGClosestTowerLocationListener : public SGPropertyChangeListener
|
|||
}
|
||||
};
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
|
||||
void initTowerLocationListener() {
|
||||
|
||||
SGPropertyChangeListener* tll = new FGTowerLocationListener();
|
||||
|
@ -160,10 +167,7 @@ static void fgApplyStartOffset(const SGGeod& aStartPos, double aHeading, double
|
|||
aHeading = aTargetHeading;
|
||||
}
|
||||
|
||||
SGGeod offset;
|
||||
double az2; // dummy
|
||||
SGGeodesy::direct(startPos, offsetAzimuth + 180, offsetDistance, offset, az2);
|
||||
startPos = offset;
|
||||
startPos = SGGeodesy::direct(startPos, offsetAzimuth + 180, offsetDistance);
|
||||
}
|
||||
|
||||
setInitialPosition(startPos, aHeading);
|
||||
|
@ -184,7 +188,7 @@ std::tuple<SGGeod, double> runwayStartPos(FGRunwayRef runway)
|
|||
// add a margin, try to keep the entire aeroplane comfortable off the
|
||||
// runway.
|
||||
double margin = startOffset + (runway->widthM() * 1.5);
|
||||
FGTaxiNodeRef taxiNode = groundNet ? groundNet->findNearestNodeOffRunway(pos, runway, margin) : 0;
|
||||
FGTaxiNodeRef taxiNode = groundNet ? groundNet->findNearestNodeOffRunway(pos, runway, margin) : FGTaxiNodeRef{};
|
||||
if (taxiNode) {
|
||||
// set this so multiplayer.nas can inform the user
|
||||
fgSetBool("/sim/presets/avoided-mp-runway", true);
|
||||
|
@ -313,11 +317,10 @@ static bool fgSetPosFromAirportIDandRwy( const string& id, const string& rwy, bo
|
|||
}
|
||||
|
||||
|
||||
static void fgSetDistOrAltFromGlideSlope() {
|
||||
// cout << "fgSetDistOrAltFromGlideSlope()" << endl;
|
||||
static void fgSetDistOrAltFromGlideSlope()
|
||||
{
|
||||
string apt_id = fgGetString("/sim/presets/airport-id");
|
||||
double gs = fgGetDouble("/sim/presets/glideslope-deg")
|
||||
* SG_DEGREES_TO_RADIANS ;
|
||||
double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg"));
|
||||
double od = fgGetDouble("/sim/presets/offset-distance-nm");
|
||||
double alt = fgGetDouble("/sim/presets/altitude-ft");
|
||||
|
||||
|
@ -363,9 +366,7 @@ static bool fgSetPosFromNAV( const string& id,
|
|||
FGPositioned::Type type,
|
||||
PositionedID guid)
|
||||
{
|
||||
FGNavRecord* nav = 0;
|
||||
|
||||
|
||||
FGNavRecordRef nav;
|
||||
if (guid != 0) {
|
||||
nav = FGPositioned::loadById<FGNavRecord>(guid);
|
||||
if (!nav)
|
||||
|
@ -383,14 +384,14 @@ static bool fgSetPosFromNAV( const string& id,
|
|||
if( navlist.size() > 1 ) {
|
||||
std::ostringstream buf;
|
||||
buf << "Ambigous NAV-ID: '" << id << "'. Specify id and frequency. Available stations:" << endl;
|
||||
for( nav_list_type::const_iterator it = navlist.begin(); it != navlist.end(); ++it ) {
|
||||
for( const auto& nav : navlist ) {
|
||||
// NDB stored in kHz, VOR stored in MHz * 100 :-P
|
||||
double factor = (*it)->type() == FGPositioned::NDB ? 1.0 : 1/100.0;
|
||||
string unit = (*it)->type() == FGPositioned::NDB ? "kHz" : "MHz";
|
||||
buf << (*it)->ident() << " "
|
||||
<< std::setprecision(5) << (double)((*it)->get_freq() * factor) << " "
|
||||
<< (*it)->get_lat() << "/" << (*it)->get_lon()
|
||||
<< endl;
|
||||
double factor = nav->type() == FGPositioned::NDB ? 1.0 : 1/100.0;
|
||||
string unit = nav->type() == FGPositioned::NDB ? "kHz" : "MHz";
|
||||
buf << nav->ident() << " "
|
||||
<< std::setprecision(5) << static_cast<double>(nav->get_freq() * factor) << " "
|
||||
<< nav->get_lat() << "/" << nav->get_lon()
|
||||
<< endl;
|
||||
}
|
||||
|
||||
SG_LOG( SG_GENERAL, SG_ALERT, buf.str() );
|
||||
|
@ -413,7 +414,7 @@ static InitPosResult setInitialPosFromCarrier( const string& carrier )
|
|||
// so our PagedLOD is loaded
|
||||
fgSetDouble("/sim/presets/longitude-deg", initialPos.second.getLongitudeDeg());
|
||||
fgSetDouble("/sim/presets/latitude-deg", initialPos.second.getLatitudeDeg());
|
||||
SG_LOG( SG_GENERAL, SG_INFO, "Initial carrier pos = " << initialPos.second );
|
||||
SG_LOG( SG_GENERAL, SG_DEBUG, "Initial carrier pos = " << initialPos.second );
|
||||
return VicinityPosition;
|
||||
}
|
||||
|
||||
|
@ -421,6 +422,31 @@ static InitPosResult setInitialPosFromCarrier( const string& carrier )
|
|||
return Failure;
|
||||
}
|
||||
|
||||
static InitPosResult checkCarrierSceneryLoaded(const SGSharedPtr<FGAICarrier> carrierRef)
|
||||
{
|
||||
SGVec3d cartPos = carrierRef->getCartPos();
|
||||
auto framestamp = globals->get_renderer()->getViewer()->getFrameStamp();
|
||||
simgear::CheckSceneryVisitor csnv(globals->get_scenery()->getPager(),
|
||||
toOsg(cartPos),
|
||||
100.0 /* range in metres */,
|
||||
framestamp);
|
||||
|
||||
// currently the PagedLODs will not be loaded by the DatabasePager
|
||||
// while the splashscreen is there, so CheckSceneryVisitor force-loads
|
||||
// missing objects in the main thread
|
||||
carrierRef->getSceneBranch()->accept(csnv);
|
||||
if (!csnv.isLoaded()) {
|
||||
return ContinueWaiting;
|
||||
}
|
||||
|
||||
// and then wait for the load to actually be synced to the main thread
|
||||
if (carrierRef->getSceneBranch()->getNumChildren() < 1) {
|
||||
return ContinueWaiting;
|
||||
}
|
||||
|
||||
return VicinityPosition;
|
||||
}
|
||||
|
||||
// Set current_options lon/lat given an aircraft carrier id
|
||||
static InitPosResult setFinalPosFromCarrier( const string& carrier, const string& posid )
|
||||
{
|
||||
|
@ -432,24 +458,9 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
|
|||
return Failure;
|
||||
}
|
||||
|
||||
SGVec3d cartPos = carrierRef->getCartPos();
|
||||
auto framestamp = globals->get_renderer()->getViewer()->getFrameStamp();
|
||||
simgear::CheckSceneryVisitor csnv(globals->get_scenery()->getPager(),
|
||||
toOsg(cartPos),
|
||||
100.0 /* range in metres */,
|
||||
framestamp);
|
||||
|
||||
// currently the PagedLODs will not be loaded by the DatabasePager
|
||||
// while the splashscreen is there, so CheckSceneryVisitor force-loads
|
||||
// missing objects in the main thread
|
||||
carrierRef->getSceneBranch()->accept(csnv);
|
||||
if (!csnv.isLoaded()) {
|
||||
return ContinueWaiting;
|
||||
}
|
||||
|
||||
// and then wait for the load to actually be synced to the main thread
|
||||
if (carrierRef->getSceneBranch()->getNumChildren() < 1) {
|
||||
return ContinueWaiting;
|
||||
auto res = checkCarrierSceneryLoaded(carrierRef);
|
||||
if (res != VicinityPosition) {
|
||||
return res; // either failrue or keep waiting for scenery load
|
||||
}
|
||||
|
||||
SGGeod geodPos;
|
||||
|
@ -492,10 +503,55 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
|
|||
return Failure;
|
||||
}
|
||||
|
||||
static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier)
|
||||
{
|
||||
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
|
||||
if (!carrierRef) {
|
||||
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = "
|
||||
<< carrier );
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto res = checkCarrierSceneryLoaded(carrierRef);
|
||||
if (res != VicinityPosition) {
|
||||
return res; // either failure or keep waiting for scenery load
|
||||
}
|
||||
|
||||
SGGeod flolsPosition;
|
||||
double headingToFLOLS;
|
||||
if (!carrierRef->getFLOLSPositionHeading(flolsPosition, headingToFLOLS)) {
|
||||
SG_LOG( SG_GENERAL, SG_ALERT, "Unable to compiute FLOLS position for carrier = "
|
||||
<< carrier );
|
||||
return Failure;
|
||||
}
|
||||
|
||||
const auto flolsElevationFt = flolsPosition.getElevationFt();
|
||||
double gs = SGMiscd::deg2rad(carrierRef->getFLOLFSGlidepathAngleDeg());
|
||||
const double od = fgGetDouble("/sim/presets/offset-distance-nm");
|
||||
|
||||
// start position, but with altitude not set
|
||||
SGGeod startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS + 180, od * SG_NM_TO_METER);
|
||||
|
||||
const double offsetFt = od * SG_NM_TO_METER * SG_METER_TO_FEET;
|
||||
startPos.setElevationFt(fabs(offsetFt*tan(gs)) + flolsElevationFt);
|
||||
|
||||
fgSetDouble("/sim/presets/longitude-deg", startPos.getLongitudeDeg());
|
||||
fgSetDouble("/sim/presets/latitude-deg", startPos.getLatitudeDeg());
|
||||
fgSetDouble("/sim/presets/altitude-ft", startPos.getElevationFt());
|
||||
fgSetDouble("/sim/presets/heading-deg", headingToFLOLS);
|
||||
fgSetDouble("/position/longitude-deg", startPos.getLongitudeDeg());
|
||||
fgSetDouble("/position/latitude-deg", startPos.getLatitudeDeg());
|
||||
fgSetDouble("/position/altitude-ft", startPos.getElevationFt());
|
||||
fgSetDouble("/orientation/heading-deg", headingToFLOLS);
|
||||
fgSetBool("/sim/presets/onground", false);
|
||||
|
||||
return ExactPosition;
|
||||
}
|
||||
|
||||
// Set current_options lon/lat given a fix ident and GUID
|
||||
static bool fgSetPosFromFix( const string& id, PositionedID guid )
|
||||
{
|
||||
FGPositioned* fix = NULL;
|
||||
FGPositionedRef fix;
|
||||
if (guid != 0) {
|
||||
fix = FGPositioned::loadById<FGPositioned>(guid);
|
||||
} else {
|
||||
|
@ -521,8 +577,7 @@ bool initPosition()
|
|||
global_callbackRegistered = true;
|
||||
}
|
||||
|
||||
double gs = fgGetDouble("/sim/presets/glideslope-deg")
|
||||
* SG_DEGREES_TO_RADIANS ;
|
||||
double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg"));
|
||||
double od = fgGetDouble("/sim/presets/offset-distance-nm");
|
||||
double alt = fgGetDouble("/sim/presets/altitude-ft");
|
||||
|
||||
|
@ -762,20 +817,29 @@ void finalizePosition()
|
|||
*/
|
||||
std::string carrier = fgGetString("/sim/presets/carrier");
|
||||
std::string parkpos = fgGetString("/sim/presets/parkpos");
|
||||
std::string runway = fgGetString("/sim/presets/runway");
|
||||
std::string apt = fgGetString("/sim/presets/airport-id");
|
||||
const bool rwy_req = fgGetBool("/sim/presets/runway-requested");
|
||||
|
||||
if (!carrier.empty())
|
||||
{
|
||||
const auto res = setFinalPosFromCarrier(carrier, parkpos);
|
||||
if (res == ExactPosition) {
|
||||
const bool atFLOLS = rwy_req && (runway == "FLOLS");
|
||||
InitPosResult carrierResult;
|
||||
if (atFLOLS) {
|
||||
carrierResult = setFinalPosFromCarrierFLOLS(carrier);
|
||||
} else {
|
||||
carrierResult = setFinalPosFromCarrier(carrier, parkpos);
|
||||
}
|
||||
if (carrierResult == ExactPosition) {
|
||||
done = true;
|
||||
} else if (res == Failure) {
|
||||
} else if (carrierResult == Failure) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "secondary carrier init failed");
|
||||
done = true;
|
||||
} else {
|
||||
done = false;
|
||||
// 60 second timeout on waiting for the carrier to load
|
||||
if (global_finalizeTime.elapsedMSec() > 60000) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "Timeout waiting for carrier scenery to load, will start on the water.");
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue