1
0
Fork 0

Merge branch 'master' into electric-engine

This commit is contained in:
Vit Hanousek 2020-04-11 09:15:21 +02:00
commit 30528a9d6c
75 changed files with 1268 additions and 1145 deletions

View file

@ -23,11 +23,7 @@ add_library(hidapi STATIC
) )
if(WIN32) if(WIN32)
find_library(SETUP_API_LIB Setupapi) target_link_libraries(hidapi PUBLIC SetupApi)
if (NOT SETUP_API_LIB)
message(FATAL_ERROR "Failed to find Setupapi.lib")
endif()
target_link_libraries(hidapi ${SETUP_API_LIB})
elseif(APPLE) elseif(APPLE)
find_library(IOKIT_FRAMEWORK IOKit) find_library(IOKIT_FRAMEWORK IOKit)
target_link_libraries(hidapi ${IOKIT_FRAMEWORK}) target_link_libraries(hidapi ${IOKIT_FRAMEWORK})

View file

@ -76,7 +76,7 @@ include(GNUInstallDirs)
# System detection/default settings # System detection/default settings
include( DetectDistro ) include( DetectDistro )
include( DetectBrowser ) include( DetectBrowser )
include( ExportDebugSymbols )
set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually d on windows") set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually d on windows")
set(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows") set(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
set(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows") set(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
@ -123,11 +123,7 @@ IF(APPLE)
find_library(COCOA_LIBRARY Cocoa) find_library(COCOA_LIBRARY Cocoa)
list(APPEND PLATFORM_LIBS ${COCOA_LIBRARY} ${CORESERVICES_LIBRARY}) list(APPEND PLATFORM_LIBS ${COCOA_LIBRARY} ${CORESERVICES_LIBRARY})
elseif(WIN32) elseif(WIN32)
find_library(SETUP_API_LIB Setupapi) set(EVENT_INPUT_DEFAULT 1)
if (SETUP_API_LIB)
set(EVENT_INPUT_DEFAULT 1)
endif()
list(APPEND PLATFORM_LIBS "Shlwapi.lib") list(APPEND PLATFORM_LIBS "Shlwapi.lib")
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "bin") set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "bin")
@ -265,14 +261,7 @@ if(EVENT_INPUT)
message(STATUS "event-based input enabled. Using ${UDEV_LIBRARIES}") message(STATUS "event-based input enabled. Using ${UDEV_LIBRARIES}")
endif() endif()
else() else()
# Windows add_definitions(-DWITH_EVENTINPUT)
if (NOT SETUP_API_LIB)
message(WARNING "Setupapi.lib not found, HID/event input is disabled")
set(ENABLE_HID_INPUT 0)
set(EVENT_INPUT 0)
else()
add_definitions(-DWITH_EVENTINPUT)
endif()
endif() endif()
if (ENABLE_HID_INPUT) if (ENABLE_HID_INPUT)
@ -282,7 +271,15 @@ if(EVENT_INPUT)
endif(EVENT_INPUT) endif(EVENT_INPUT)
if (ENABLE_SWIFT) if (ENABLE_SWIFT)
# DBUS # DBUS
# our local FindDBus.cmake file uses pkg-config on non-Windows
# we want to ensure our local prefixes are searched, so set this
set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH 1)
# unfortunately CMAKE_INSTALL_PREFIX is not searched, so add that manually
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX})
find_package(DBus) find_package(DBus)
#libevent #libevent
@ -317,6 +314,13 @@ find_package(OpenSceneGraph 3.2.0 REQUIRED
osgGA osgGA
) )
find_package(sentry QUIET)
if (TARGET sentry::sentry)
message(STATUS "Sentry.io crash reporting enabled")
set(HAVE_SENTRY 1)
endif()
if (MSVC) if (MSVC)
find_package(CrashRpt) find_package(CrashRpt)
if (CRASHRPT_FOUND) if (CRASHRPT_FOUND)
@ -415,10 +419,7 @@ endif()
if(ENABLE_RTI) if(ENABLE_RTI)
message(STATUS "RTI: ENABLED") message(STATUS "RTI: ENABLED")
find_package(RTI) set(FG_HAVE_HLA 1)
if(RTI_FOUND)
set(FG_HAVE_HLA 1)
endif(RTI_FOUND)
else() else()
message(STATUS "RTI: DISABLED") message(STATUS "RTI: DISABLED")
endif(ENABLE_RTI) endif(ENABLE_RTI)

View file

@ -48,6 +48,9 @@ if (MSVC AND MSVC_3RDPARTY_ROOT)
set (CMAKE_LIBRARY_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/lib ) set (CMAKE_LIBRARY_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/lib )
set (CMAKE_INCLUDE_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/include) set (CMAKE_INCLUDE_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/include)
# ensure 3rdparty/lib/cmake is searched
list(APPEND CMAKE_PREFIX_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR})
if(NOT BOOST_INCLUDEDIR) if(NOT BOOST_INCLUDEDIR)
# if this variable was not set by the user, set it to 3rdparty root's # if this variable was not set by the user, set it to 3rdparty root's
# parent dir, which is the normal location for people using our # parent dir, which is the normal location for people using our

View file

@ -0,0 +1,22 @@
# placehodler target for other ones to depend upon
add_custom_target(
debug_symbols
)
function(export_debug_symbols target)
if (APPLE)
add_custom_target(${target}.dSYM
COMMENT "Generating dSYM files for ${target}"
COMMAND dsymutil --out=${target}.dSYM $<TARGET_FILE:${target}>
DEPENDS $<TARGET_FILE:${target}>
)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${target}.dSYM DESTINATION symbols OPTIONAL)
add_dependencies(debug_symbols ${target}.dSYM)
endif()
endfunction()

View file

@ -7,7 +7,7 @@ function(setup_fgfs_bundle target)
# in our local CMakeModules dir # in our local CMakeModules dir
set_target_properties(${target} PROPERTIES set_target_properties(${target} PROPERTIES
MACOSX_BUNDLE_INFO_PLIST FlightGearBundleInfo.plist.in MACOSX_BUNDLE_INFO_PLIST FlightGearBundleInfo.plist.in
MACOSX_BUNDLE_GUI_IDENTIFIER "org.flightgear.FlightGear" MACOSX_BUNDLE_GUI_IDENTIFIER "org.flightgear.mac-nightly"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${FLIGHTGEAR_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${FLIGHTGEAR_VERSION}
MACOSX_BUNDLE_LONG_VERSION_STRING "FlightGear ${FLIGHTGEAR_VERSION} Nightly" MACOSX_BUNDLE_LONG_VERSION_STRING "FlightGear ${FLIGHTGEAR_VERSION} Nightly"
MACOSX_BUNDLE_BUNDLE_VERSION ${FLIGHTGEAR_VERSION} MACOSX_BUNDLE_BUNDLE_VERSION ${FLIGHTGEAR_VERSION}

View file

@ -7,7 +7,7 @@ function(setup_fgfs_libraries target)
#message(STATUS "SG libs ${SIMGEAR_LIBRARIES}") #message(STATUS "SG libs ${SIMGEAR_LIBRARIES}")
if(RTI_FOUND) if(RTI_FOUND)
set(HLA_LIBRARIES ${RTI_LIBRARIES}) set(HLA_LIBRARIES ${RTI_LDFLAGS})
else() else()
set(HLA_LIBRARIES "") set(HLA_LIBRARIES "")
endif() endif()

View file

@ -20,15 +20,12 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H #include <config.h>
# include <config.h>
#endif
#include <string.h> #include <string.h>
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <boost/foreach.hpp>
#include <string> #include <string>
#include <osg/ref_ptr> #include <osg/ref_ptr>
@ -67,11 +64,9 @@ public:
} }
~FGAIModelData() ~FGAIModelData() = default;
{
}
virtual FGAIModelData* clone() const { return new FGAIModelData(); } FGAIModelData* clone() const override { return new FGAIModelData(); }
/** osg callback, thread-safe */ /** osg callback, thread-safe */
void modelLoaded(const std::string& path, SGPropertyNode *prop, osg::Node *n) void modelLoaded(const std::string& path, SGPropertyNode *prop, osg::Node *n)

View file

@ -72,7 +72,7 @@ void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
_flolsPosOffset(2) = - flols->getDoubleValue("z-offset-m", 0); _flolsPosOffset(2) = - flols->getDoubleValue("z-offset-m", 0);
_flolsHeadingOffsetDeg = flols->getDoubleValue("heading-offset-deg", 0.0); _flolsHeadingOffsetDeg = flols->getDoubleValue("heading-offset-deg", 0.0);
_flolsApproachAngle = flols->getDoubleValue("glidepath-angle-deg", 3.0); _flolsApproachAngle = flols->getDoubleValue("glidepath-angle-deg", 3.5);
} else } else
_flolsPosOffset = SGVec3d::zeros(); _flolsPosOffset = SGVec3d::zeros();
@ -621,7 +621,7 @@ std::pair<bool, SGGeod> FGAICarrier::initialPositionForCarrier(const std::string
// this is actually a three-layer search (we want the scenario with the // this is actually a three-layer search (we want the scenario with the
// carrier with the correct penanant or name. Sometimes an XPath for // carrier with the correct penanant or name. Sometimes an XPath for
// properties would be quite handy :) // properties would be quite handy :)
for (auto s : fgGetNode("/sim/ai/scenarios")->getChildren("scenario")) { for (auto s : fgGetNode("/sim/ai/scenarios")->getChildren("scenario")) {
auto carriers = s->getChildren("carrier"); auto carriers = s->getChildren("carrier");
auto it = std::find_if(carriers.begin(), carriers.end(), auto it = std::find_if(carriers.begin(), carriers.end(),
@ -636,16 +636,16 @@ std::pair<bool, SGGeod> FGAICarrier::initialPositionForCarrier(const std::string
if (it == carriers.end()) { if (it == carriers.end()) {
continue; continue;
} }
// mark the scenario for loading (which will happen in post-init of the AIManager) // mark the scenario for loading (which will happen in post-init of the AIManager)
fgGetNode("/sim/ai/")->addChild("scenario")->setStringValue(s->getStringValue("id")); fgGetNode("/sim/ai/")->addChild("scenario")->setStringValue(s->getStringValue("id"));
// read out the initial-position // read out the initial-position
SGGeod geod = SGGeod::fromDeg((*it)->getDoubleValue("longitude"), SGGeod geod = SGGeod::fromDeg((*it)->getDoubleValue("longitude"),
(*it)->getDoubleValue("latitude")); (*it)->getDoubleValue("latitude"));
return std::make_pair(true, geod); return std::make_pair(true, geod);
} // of scenarios iteration } // of scenarios iteration
return std::make_pair(false, SGGeod()); return std::make_pair(false, SGGeod());
} }
@ -655,7 +655,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
if (!aiManager) { if (!aiManager) {
return {}; return {};
} }
for (const auto& aiObject : aiManager->get_ai_list()) { for (const auto& aiObject : aiManager->get_ai_list()) {
if (aiObject->isa(FGAIBase::otCarrier)) { if (aiObject->isa(FGAIBase::otCarrier)) {
SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get()); SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get());
@ -664,7 +664,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
} }
} }
} // of all objects iteration } // of all objects iteration
return {}; return {};
} }
@ -673,19 +673,22 @@ void FGAICarrier::extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGProp
for (auto c : xmlNode->getChildren("entry")) { for (auto c : xmlNode->getChildren("entry")) {
if (c->getStringValue("type") != std::string("carrier")) if (c->getStringValue("type") != std::string("carrier"))
continue; continue;
const std::string name = c->getStringValue("name"); const std::string name = c->getStringValue("name");
const std::string pennant = c->getStringValue("pennant-number"); const std::string pennant = c->getStringValue("pennant-number");
if (name.empty() && pennant.empty()) { if (name.empty() && pennant.empty()) {
continue; continue;
} }
SGPropertyNode_ptr carrierNode = scenario->addChild("carrier"); SGPropertyNode_ptr carrierNode = scenario->addChild("carrier");
// extract the initial position from the scenario // extract the initial position from the scenario
carrierNode->setDoubleValue("longitude", c->getDoubleValue("longitude")); carrierNode->setDoubleValue("longitude", c->getDoubleValue("longitude"));
carrierNode->setDoubleValue("latitude", c->getDoubleValue("latitude")); carrierNode->setDoubleValue("latitude", c->getDoubleValue("latitude"));
// A description of the carrier is also available from the entry. Primarily for use by the launcher
carrierNode->setStringValue("description", c->getStringValue("description"));
// the find code above just looks for anything called a name (so alias // the find code above just looks for anything called a name (so alias
// are possible, for example) // are possible, for example)
if (!name.empty()) carrierNode->addChild("name")->setStringValue(name); if (!name.empty()) carrierNode->addChild("name")->setStringValue(name);

View file

@ -234,6 +234,13 @@ FGTaxiNodeRef FGGroundNetwork::findNearestNodeOffRunway(const SGGeod& aGeod, FGR
[runwayLine, cartPos, marginMSqr] (const FGTaxiNodeRef& a) [runwayLine, cartPos, marginMSqr] (const FGTaxiNodeRef& a)
{ {
if (a->getIsOnRunway()) return false; if (a->getIsOnRunway()) return false;
// exclude parking positions from consideration. This helps to
// exclude airports whose ground nets only list parking positions,
// since these typically produce bad results. See discussion in
// https://sourceforge.net/p/flightgear/codetickets/2110/
if (a->type() == FGPositioned::PARKING) return false;
return (distSqr(runwayLine, a->cart()) >= marginMSqr); return (distSqr(runwayLine, a->cart()) >= marginMSqr);
}); });

View file

@ -153,7 +153,7 @@ public:
_nd(nd) _nd(nd)
{} {}
virtual void valueChanged (SGPropertyNode * prop) void valueChanged (SGPropertyNode * prop) override
{ {
_nd->invalidatePositionedCache(); _nd->invalidatePositionedCache();
} }
@ -168,7 +168,7 @@ public:
_nd(nd) _nd(nd)
{} {}
virtual void valueChanged (SGPropertyNode * prop) void valueChanged (SGPropertyNode * prop) override
{ {
_nd->forceUpdate(); _nd->forceUpdate();
} }
@ -256,7 +256,7 @@ public:
// record instances for limiting by count // record instances for limiting by count
int instanceCount; int instanceCount;
private: private:
SymbolDef* definition; SymbolDef* definition = nullptr;
std::unique_ptr<SGCondition> enable; std::unique_ptr<SGCondition> enable;
string_set required_states; string_set required_states;
@ -459,7 +459,7 @@ NavDisplay::NavDisplay(SGPropertyNode *node) :
SGPropertyNode* symbol; SGPropertyNode* symbol;
map<string, SymbolDef*> definitionDict; map<string, SymbolDef*> definitionDict;
for (int i = 0; (symbol = symbolsNode->getChild("symbol", i)) != NULL; ++i) { for (int i = 0; (symbol = symbolsNode->getChild("symbol", i)) != nullptr; ++i) {
SymbolDef* def = new SymbolDef; SymbolDef* def = new SymbolDef;
if (!def->initFromNode(symbol, this)) { if (!def->initFromNode(symbol, this)) {
delete def; delete def;
@ -539,7 +539,7 @@ NavDisplay::init ()
} }
// no mipmap or else alpha will mix with pixels on the border of shapes, ruining the effect // no mipmap or else alpha will mix with pixels on the border of shapes, ruining the effect
_symbolTexture = SGLoadTexture2D(tpath, NULL, false, false); _symbolTexture = SGLoadTexture2D(tpath, nullptr, false, false);
_odg = new FGODGauge; _odg = new FGODGauge;
_odg->setSize(_Instrument->getIntValue("texture-size", 512)); _odg->setSize(_Instrument->getIntValue("texture-size", 512));
@ -958,7 +958,7 @@ public:
double minRunwayLengthFt; double minRunwayLengthFt;
virtual bool pass(FGPositioned* aPos) const bool pass(FGPositioned* aPos) const override
{ {
if (aPos->type() == FGPositioned::FIX) { if (aPos->type() == FGPositioned::FIX) {
string ident(aPos->ident()); string ident(aPos->ident());
@ -979,11 +979,13 @@ public:
return _owner->isPositionedShown(aPos); return _owner->isPositionedShown(aPos);
} }
virtual FGPositioned::Type minType() const { FGPositioned::Type minType() const override
{
return FGPositioned::AIRPORT; return FGPositioned::AIRPORT;
} }
virtual FGPositioned::Type maxType() const { FGPositioned::Type maxType() const override
{
return FGPositioned::OBSTACLE; return FGPositioned::OBSTACLE;
} }
@ -1105,7 +1107,7 @@ FGNavRecord* NavDisplay::processNavRadio(const SGPropertyNode_ptr& radio)
FGNavRecord* nav = FGNavList::findByFreq(mhz, _pos, FGNavList::navFilter()); FGNavRecord* nav = FGNavList::findByFreq(mhz, _pos, FGNavList::navFilter());
if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) { if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
// station was not found // station was not found
return NULL; return nullptr;
} }
@ -1367,11 +1369,11 @@ void NavDisplay::computeAIStates(const SGPropertyNode* ai, string_set& states)
SymbolInstance* NavDisplay::addSymbolInstance(const osg::Vec2& proj, double heading, SymbolDef* def, SGPropertyNode* vars) SymbolInstance* NavDisplay::addSymbolInstance(const osg::Vec2& proj, double heading, SymbolDef* def, SGPropertyNode* vars)
{ {
if (isProjectedClipped(proj)) { if (isProjectedClipped(proj)) {
return NULL; return nullptr;
} }
if ((def->limitCount > 0) && (def->instanceCount >= def->limitCount)) { if ((def->limitCount > 0) && (def->instanceCount >= def->limitCount)) {
return NULL; return nullptr;
} }
++def->instanceCount; ++def->instanceCount;
@ -1416,28 +1418,28 @@ void NavDisplay::addTestSymbols()
double dummy; double dummy;
SGGeodesy::direct(_pos, 45.0, 20.0 * SG_NM_TO_METER, a1, dummy); SGGeodesy::direct(_pos, 45.0, 20.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("airport", "", a1, 0.0, NULL); addTestSymbol("airport", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 95.0, 40.0 * SG_NM_TO_METER, a1, dummy); SGGeodesy::direct(_pos, 95.0, 40.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("vor", "", a1, 0.0, NULL); addTestSymbol("vor", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 120, 80.0 * SG_NM_TO_METER, a1, dummy); SGGeodesy::direct(_pos, 120, 80.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("airport", "destination", a1, 0.0, NULL); addTestSymbol("airport", "destination", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 80.0, 20.0 * SG_NM_TO_METER, a1, dummy); SGGeodesy::direct(_pos, 80.0, 20.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL); addTestSymbol("fix", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 140.0, 20.0 * SG_NM_TO_METER, a1, dummy); SGGeodesy::direct(_pos, 140.0, 20.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL); addTestSymbol("fix", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 110.0, 10.0 * SG_NM_TO_METER, a1, dummy); SGGeodesy::direct(_pos, 110.0, 10.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL); addTestSymbol("fix", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 110.0, 5.0 * SG_NM_TO_METER, a1, dummy); SGGeodesy::direct(_pos, 110.0, 5.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL); addTestSymbol("fix", "", a1, 0.0, nullptr);
} }
void NavDisplay::addRule(SymbolRule* r) void NavDisplay::addRule(SymbolRule* r)

View file

@ -184,6 +184,9 @@ MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
_tiedProperties.Tie("decoded", this, &MetarProperties::get_decoded ); _tiedProperties.Tie("decoded", this, &MetarProperties::get_decoded );
_tiedProperties.Tie("cavok", &_cavok ); _tiedProperties.Tie("cavok", &_cavok );
_tiedProperties.Tie("description", this, &MetarProperties::get_description ); _tiedProperties.Tie("description", this, &MetarProperties::get_description );
// mark proeprties as listener-safe, we invoke valueChanged explicitly
_tiedProperties.setAttribute(SGPropertyNode::LISTENER_SAFE, true);
} }
MetarProperties::~MetarProperties() MetarProperties::~MetarProperties()
@ -422,6 +425,7 @@ void MetarProperties::setMetar( SGSharedPtr<FGMetar> m )
_hour = m->getHour(); _hour = m->getHour();
_minute = m->getMinute(); _minute = m->getMinute();
_cavok = m->getCAVOK(); _cavok = m->getCAVOK();
_tiedProperties.fireValueChanged();
_metarValidNode->setBoolValue(true); _metarValidNode->setBoolValue(true);
_description = m->getDescription(-1); _description = m->getDescription(-1);
} }

View file

@ -204,9 +204,9 @@ void FGFDM::init()
fgSetDouble(buf, CM2GALS * _airplane.getTankCapacity(i)/density); fgSetDouble(buf, CM2GALS * _airplane.getTankCapacity(i)/density);
} }
// This has a nasty habit of being false at startup. That's not if (_yasimN->getBoolValue("respect-external-gear-state") == false) {
// good. fgSetBool("/controls/gear/gear-down", true);
fgSetBool("/controls/gear/gear-down", true); }
_airplane.getModel()->setTurbulence(_turb); _airplane.getModel()->setTurbulence(_turb);
} }

View file

@ -196,8 +196,10 @@ void YASim::init()
// Are we at ground level? If so, lift the plane up so the gear // Are we at ground level? If so, lift the plane up so the gear
// clear the ground. // clear the ground.
bool respect_external_gear_state = fgGetBool("/fdm/yasim/respect-external-gear-state");
double runway_altitude = get_Runway_altitude(); double runway_altitude = get_Runway_altitude();
if(get_Altitude() - runway_altitude < 50) { if(get_Altitude() - runway_altitude < 50) {
bool gear_state = fgGetBool("/controls/gear/gear-down");
fgSetBool("/controls/gear/gear-down", false); fgSetBool("/controls/gear/gear-down", false);
float minGearZ = 1e18; float minGearZ = 1e18;
for(int i=0; i<airplane->numGear(); i++) { for(int i=0; i<airplane->numGear(); i++) {
@ -209,9 +211,9 @@ void YASim::init()
} }
_set_Altitude(runway_altitude - minGearZ*M2FT); _set_Altitude(runway_altitude - minGearZ*M2FT);
// ground start-up: gear down // ground start-up: gear down
fgSetBool("/controls/gear/gear-down", true); fgSetBool("/controls/gear/gear-down", respect_external_gear_state ? gear_state : true);
} }
else else if (! respect_external_gear_state)
{ {
// airborne start-up: gear up // airborne start-up: gear up
fgSetBool("/controls/gear/gear-down", false); fgSetBool("/controls/gear/gear-down", false);
@ -300,7 +302,7 @@ void YASim::copyToYASim(bool copyState)
atmo.setDensity(dens); atmo.setDensity(dens);
atmo.setTemperature(temp); atmo.setTemperature(temp);
atmo.setPressure(pressure); atmo.setPressure(pressure);
// Convert and set: // Convert and set:
Model* model = _fdm->getAirplane()->getModel(); Model* model = _fdm->getAirplane()->getModel();
yasim::State s; yasim::State s;
@ -380,9 +382,9 @@ void YASim::copyToYASim(bool copyState)
// These are set below: // These are set below:
// _set_Accels_Local // _set_Accels_Local
// _set_Accels_Body // _set_Accels_Body
// _set_Accels_CG_Body // _set_Accels_CG_Body
// _set_Accels_Pilot_Body // _set_Accels_Pilot_Body
// _set_Accels_CG_Body_N // _set_Accels_CG_Body_N
// _set_Velocities_Local // _set_Velocities_Local
// _set_Velocities_Ground // _set_Velocities_Ground
// _set_Velocities_Body // _set_Velocities_Body

View file

@ -38,6 +38,7 @@
#include <Include/version.h> #include <Include/version.h>
#include "QmlAircraftInfo.hxx" #include "QmlAircraftInfo.hxx"
#include "FavouriteAircraftData.hxx"
using namespace simgear::pkg; using namespace simgear::pkg;
@ -125,7 +126,7 @@ private:
return QModelIndex(); return QModelIndex();
} }
int offset = std::distance(m_model->m_packages.begin(), it); const int offset = static_cast<int>(std::distance(m_model->m_packages.begin(), it));
return m_model->index(offset + m_model->m_cachedLocalAircraftCount); return m_model->index(offset + m_model->m_cachedLocalAircraftCount);
} }
@ -138,6 +139,8 @@ void PackageDelegate::catalogRefreshed(CatalogRef aCatalog, StatusCode aReason)
// nothing to do // nothing to do
} else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) { } else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) {
m_model->refreshPackages(); m_model->refreshPackages();
} else if (aReason == FAIL_VERSION) {
// silent about this
} else { } else {
qWarning() << "failed refresh of" qWarning() << "failed refresh of"
<< QString::fromStdString(aCatalog->url()) << ":" << aReason << endl; << QString::fromStdString(aCatalog->url()) << ":" << aReason << endl;
@ -158,8 +161,6 @@ AircraftItemModel::AircraftItemModel(QObject* pr) :
this, &AircraftItemModel::onScanAddedItems); this, &AircraftItemModel::onScanAddedItems);
connect(cache, &LocalAircraftCache::cleared, connect(cache, &LocalAircraftCache::cleared,
this, &AircraftItemModel::onLocalCacheCleared); this, &AircraftItemModel::onLocalCacheCleared);
loadFavourites();
} }
AircraftItemModel::~AircraftItemModel() AircraftItemModel::~AircraftItemModel()
@ -244,7 +245,7 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
if (role == AircraftIsFavouriteRole) { if (role == AircraftIsFavouriteRole) {
// recursive call here, hope that's okay // recursive call here, hope that's okay
const auto uri = data(index, AircraftURIRole).toUrl(); const auto uri = data(index, AircraftURIRole).toUrl();
return m_favourites.contains(uri); return FavouriteAircraftData::instance()->isFavourite(uri);
} }
if (row >= m_cachedLocalAircraftCount) { if (row >= m_cachedLocalAircraftCount) {
@ -338,7 +339,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta
QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const DelegateState& state, int role) const QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const DelegateState& state, int role) const
{ {
if (role >= AircraftVariantDescriptionRole) { if (role >= AircraftVariantDescriptionRole) {
int variantIndex = role - AircraftVariantDescriptionRole; const unsigned int variantIndex = static_cast<unsigned int>(role - AircraftVariantDescriptionRole);
QString desc = QString::fromStdString(item->nameForVariant(variantIndex)); QString desc = QString::fromStdString(item->nameForVariant(variantIndex));
if (desc.isEmpty()) { if (desc.isEmpty()) {
desc = tr("Missing description for: %1").arg(QString::fromStdString(item->id())); desc = tr("Missing description for: %1").arg(QString::fromStdString(item->id()));
@ -431,17 +432,11 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value,
emit dataChanged(index, index); emit dataChanged(index, index);
return true; return true;
} else if (role == AircraftIsFavouriteRole) { } else if (role == AircraftIsFavouriteRole) {
bool f = value.toBool();
const auto uri = data(index, AircraftURIRole).toUrl(); const auto uri = data(index, AircraftURIRole).toUrl();
const auto cur = m_favourites.contains(uri); bool changed = FavouriteAircraftData::instance()->setFavourite(uri, value.toBool());
if (f && !cur) { if (changed) {
m_favourites.append(uri); emit dataChanged(index, index);
} else if (!f && cur) {
m_favourites.removeOne(uri);
} }
saveFavourites();
emit dataChanged(index, index);
} }
return false; return false;
@ -493,9 +488,10 @@ QModelIndex AircraftItemModel::indexOfAircraftURI(QUrl uri) const
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString()); PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) { if (pkg) {
for (size_t i=0; i < m_packages.size(); ++i) { const auto numPackages = m_packages.size();
if (m_packages[i] == pkg) { for (unsigned int i=0; i < numPackages; ++i) {
return index(rowOffset + i); if (m_packages.at(i) == pkg) {
return index(static_cast<int>(rowOffset + i));
} }
} // of linear package scan } // of linear package scan
} }
@ -542,7 +538,7 @@ void AircraftItemModel::selectVariantForAircraftURI(QUrl uri)
if (pkg) { if (pkg) {
for (size_t i=0; i < m_packages.size(); ++i) { for (size_t i=0; i < m_packages.size(); ++i) {
if (m_packages[i] == pkg) { if (m_packages[i] == pkg) {
modelIndex = index(rowOffset + static_cast<int>(i)); modelIndex = index(rowOffset + static_cast<int>(i));
variantIndex = pkg->indexOfVariant(ident.toStdString()); variantIndex = pkg->indexOfVariant(ident.toStdString());
break; break;
} }
@ -663,21 +659,3 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
return !ex->isDownloading(); return !ex->isDownloading();
} }
void AircraftItemModel::loadFavourites()
{
m_favourites.clear();
QSettings settings;
Q_FOREACH(auto v, settings.value("favourite-aircraft").toList()) {
m_favourites.append(v.toUrl());
}
}
void AircraftItemModel::saveFavourites()
{
QVariantList favs;
Q_FOREACH(auto u, m_favourites) {
favs.append(u);
}
QSettings settings;
settings.setValue("favourite-aircraft", favs);
}

View file

@ -146,9 +146,6 @@ private:
void installSucceeded(QModelIndex index); void installSucceeded(QModelIndex index);
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason); void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
void loadFavourites();
void saveFavourites();
private: private:
PackageDelegate* m_delegate = nullptr; PackageDelegate* m_delegate = nullptr;
@ -156,8 +153,6 @@ private:
simgear::pkg::RootRef m_packageRoot; simgear::pkg::RootRef m_packageRoot;
simgear::pkg::PackageList m_packages; simgear::pkg::PackageList m_packages;
QVector<QUrl> m_favourites;
int m_cachedLocalAircraftCount = 0; int m_cachedLocalAircraftCount = 0;
}; };

View file

@ -4,6 +4,8 @@
#include <QDebug> #include <QDebug>
#include "AircraftModel.hxx" #include "AircraftModel.hxx"
#include "FavouriteAircraftData.hxx"
#include <simgear/package/Package.hxx> #include <simgear/package/Package.hxx>
AircraftProxyModel::AircraftProxyModel(QObject *pr, QAbstractItemModel * source) : AircraftProxyModel::AircraftProxyModel(QObject *pr, QAbstractItemModel * source) :
@ -121,6 +123,13 @@ void AircraftProxyModel::setShowFavourites(bool e)
return; return;
m_onlyShowFavourites = e; m_onlyShowFavourites = e;
if (e) {
setDynamicSortFilter(false);
connect(FavouriteAircraftData::instance(), &FavouriteAircraftData::changed,
[this]() {
this->invalidate();
});
}
invalidate(); invalidate();
} }

View file

@ -68,7 +68,7 @@ protected:
private: private:
bool filterAircraft(const QModelIndex& sourceIndex) const; bool filterAircraft(const QModelIndex& sourceIndex) const;
bool m_ratingsFilter = true; bool m_ratingsFilter = false;
bool m_onlyShowInstalled = false; bool m_onlyShowInstalled = false;
bool m_onlyShowWithUpdate = false; bool m_onlyShowWithUpdate = false;
bool m_onlyShowFavourites = false; bool m_onlyShowFavourites = false;

View file

@ -155,6 +155,25 @@ void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int h
painter->restore(); painter->restore();
} }
void BaseDiagram::paintCarrierIcon(QPainter* painter, const SGGeod& geod, int headingDeg)
{
QPointF pos = project(geod);
QPixmap pix(":/svg/aircraft-carrier");
pos = m_viewportTransform.map(pos);
painter->save();
painter->setWorldTransform(m_baseDeviceTransform);
painter->translate(pos.x(), pos.y());
painter->rotate(headingDeg);
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
QRect carrierIconRect = pix.rect();
carrierIconRect.moveCenter(QPoint(0,0));
painter->drawPixmap(carrierIconRect, pix);
painter->restore();
}
void BaseDiagram::paintPolygonData(QPainter* painter) void BaseDiagram::paintPolygonData(QPainter* painter)
{ {
QTransform invT = m_viewportTransform.inverted(); QTransform invT = m_viewportTransform.inverted();

View file

@ -57,7 +57,7 @@ public:
void setAircraftType(LauncherController::AircraftType type); void setAircraftType(LauncherController::AircraftType type);
QRect rect() const; QRect rect() const;
Q_INVOKABLE void resetZoom(); Q_INVOKABLE void resetZoom();
protected: protected:
void paint(QPainter* p) override; void paint(QPainter* p) override;
@ -78,7 +78,7 @@ protected:
void extendBounds(const QPointF& p, double radiusM = 1.0); void extendBounds(const QPointF& p, double radiusM = 1.0);
QPointF project(const SGGeod& geod) const; QPointF project(const SGGeod& geod) const;
QTransform transform() const; QTransform transform() const;
void clearIgnoredNavaids(); void clearIgnoredNavaids();
void addIgnoredNavaid(FGPositionedRef pos); void addIgnoredNavaid(FGPositionedRef pos);
@ -98,6 +98,7 @@ protected:
static SGGeod unproject(const QPointF &xy, const SGGeod &center); static SGGeod unproject(const QPointF &xy, const SGGeod &center);
void paintAirplaneIcon(QPainter *painter, const SGGeod &geod, int headingDeg); void paintAirplaneIcon(QPainter *painter, const SGGeod &geod, int headingDeg);
void paintCarrierIcon(QPainter *painter, const SGGeod &geod, int headingDeg);
void paintAirways(QPainter* painter, const FGPositionedList& navs); void paintAirways(QPainter* painter, const FGPositionedList& navs);
QPointF projectedPosition(PositionedID pid) const; QPointF projectedPosition(PositionedID pid) const;

View file

@ -84,6 +84,8 @@ if (HAVE_QT)
BaseDiagram.hxx BaseDiagram.hxx
AirportDiagram.cxx AirportDiagram.cxx
AirportDiagram.hxx AirportDiagram.hxx
CarrierDiagram.cxx
CarrierDiagram.hxx
NavaidDiagram.cxx NavaidDiagram.cxx
NavaidDiagram.hxx NavaidDiagram.hxx
SetupRootDialog.cxx SetupRootDialog.cxx
@ -128,6 +130,8 @@ if (HAVE_QT)
PathListModel.hxx PathListModel.hxx
CarriersLocationModel.cxx CarriersLocationModel.cxx
CarriersLocationModel.hxx CarriersLocationModel.hxx
FavouriteAircraftData.cxx
FavouriteAircraftData.hxx
${uic_sources} ${uic_sources}
${qrc_sources} ${qrc_sources}
${qml_sources}) ${qml_sources})
@ -137,7 +141,7 @@ if (HAVE_QT)
target_include_directories(fglauncher PRIVATE ${PROJECT_BINARY_DIR}/src/GUI) target_include_directories(fglauncher PRIVATE ${PROJECT_BINARY_DIR}/src/GUI)
add_library(fgqmlui QQuickDrawable.cxx add_library(fgqmlui QQuickDrawable.cxx
QQuickDrawable.hxx QQuickDrawable.hxx
QtQuickFGCanvasItem.cxx QtQuickFGCanvasItem.cxx
QtQuickFGCanvasItem.hxx QtQuickFGCanvasItem.hxx
PropertyItemModel.cxx PropertyItemModel.cxx

148
src/GUI/CarrierDiagram.cxx Normal file
View file

@ -0,0 +1,148 @@
// CarrierDiagram.cxx - part of GUI launcher using Qt5
//
// Written by Stuart Buchanan, started April 2020.
//
// Copyright (C) 2022 Stuart Buchanan <stuart13@gmail.com>
//
// 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 "CarrierDiagram.hxx"
#include <limits>
#include <QPainter>
#include <QDebug>
#include <QVector2D>
#include <QMouseEvent>
#include <Navaids/NavDataCache.hxx>
CarrierDiagram::CarrierDiagram(QQuickItem* pr) :
BaseDiagram(pr)
{
}
void CarrierDiagram::setGeod(QmlGeod geod)
{
setGeod(geod.geod());
}
QmlGeod CarrierDiagram::geod() const
{
return QmlGeod(m_geod);
}
void CarrierDiagram::setGeod(const SGGeod &geod)
{
m_Carrier.clear();
m_geod = geod;
m_projectionCenter = m_geod;
recomputeBounds(true);
emit locationChanged();
}
void CarrierDiagram::setOffsetEnabled(bool offset)
{
if (m_offsetEnabled == offset)
return;
m_offsetEnabled = offset;
recomputeBounds(true);
emit offsetChanged();
}
void CarrierDiagram::setAbeam(bool abeam)
{
if (m_abeam == abeam)
return;
m_abeam = abeam;
recomputeBounds(true);
emit offsetChanged();
}
void CarrierDiagram::setOffsetDistance(QuantityValue distanceNm)
{
if (distanceNm == m_offsetDistance)
return;
m_offsetDistance = distanceNm;
update();
emit offsetChanged();
}
void CarrierDiagram::paintContents(QPainter *painter)
{
QPointF base = project(m_geod);
SGGeod carrierPos = m_geod;
SGGeod aircraftPos = m_geod;
float aircraft_heading = 0;
if (m_offsetEnabled) {
// We don't actually know the eventual orientation of the carrier,
// so we place the aircraft relative to the aircraft carrier icon,
// which has the angled flight deck at 80 degrees (e.g. just N or E)
//
// There are two case:
// - On finals, which will be at -100 degrees, aircraft heading 80
// - Abeam on a left hand circuit, so offset 0 degrees,
// aircraft heading downwind -90 (note that this is parallel to the
// _carrier_, not the angled flightdeck)
float offset_heading = -100;
aircraft_heading = 80;
if (m_abeam) {
offset_heading = 0;
aircraft_heading = -90;
}
double d = m_offsetDistance.convertToUnit(Units::Kilometers).value * 1000;
SGGeod offsetGeod = SGGeodesy::direct(m_geod, offset_heading, d);
QPointF offset = project(offsetGeod);
QPen pen(Qt::green);
pen.setCosmetic(true);
painter->setPen(pen);
painter->drawLine(base, offset);
aircraftPos = offsetGeod;
} else {
// We're at a parking position or on the catapults, so simply rotate to
// match the carrier heading - E
aircraft_heading = 90;
}
paintCarrierIcon(painter, carrierPos, 0.0);
paintAirplaneIcon(painter, aircraftPos, aircraft_heading);
}
void CarrierDiagram::doComputeBounds()
{
extendBounds(project(m_geod));
// project four points around the base location at 20nm to give some
// coverage
for (int i=0; i<4; ++i) {
SGGeod pt = SGGeodesy::direct(m_geod, i * 90, SG_NM_TO_METER * 20.0);
extendBounds(project(pt));
}
if (m_offsetEnabled) {
double d = m_offsetDistance.convertToUnit(Units::Kilometers).value * 1000;
float offset_heading = m_abeam ? 0 : -100; // See above for explanation
SGGeod offsetPos = SGGeodesy::direct(m_geod, offset_heading, d);
extendBounds(project(offsetPos));
}
}

View file

@ -0,0 +1,80 @@
// CarrierDiagram.hxx - part of GUI launcher using Qt5
//
// Written by Stuart Buchanan, started April 2020.
//
// Copyright (C) 2022 Stuart Buchanan <stuart13@gmail.com>
//
// 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_CARRIER_DIAGRAM_HXX
#define GUI_CARRIER_DIAGRAM_HXX
#include "BaseDiagram.hxx"
#include "QmlPositioned.hxx"
#include "UnitsModel.hxx"
#include <Navaids/navrecord.hxx>
#include <simgear/math/sg_geodesy.hxx>
class CarrierDiagram : public BaseDiagram
{
Q_OBJECT
Q_PROPERTY(QmlGeod geod READ geod WRITE setGeod NOTIFY locationChanged)
Q_PROPERTY(bool offsetEnabled READ isOffsetEnabled WRITE setOffsetEnabled NOTIFY offsetChanged)
Q_PROPERTY(bool abeam READ isAbeam WRITE setAbeam NOTIFY offsetChanged)
Q_PROPERTY(QuantityValue offsetDistance READ offsetDistance WRITE setOffsetDistance NOTIFY offsetChanged)
public:
CarrierDiagram(QQuickItem* pr = nullptr);
//void setCarrier(qlonglong nav);
//qlonglong navaid() const;
void setGeod(QmlGeod geod);
QmlGeod geod() const;
void setGeod(const SGGeod& geod);
bool isOffsetEnabled() const
{ return m_offsetEnabled; }
bool isAbeam() const
{ return m_abeam; }
void setOffsetEnabled(bool offset);
void setAbeam(bool abeam);
void setOffsetDistance(QuantityValue distance);
QuantityValue offsetDistance() const
{ return m_offsetDistance; }
signals:
void locationChanged();
void offsetChanged();
protected:
void paintContents(QPainter *) override;
void doComputeBounds() override;
private:
FGPositionedRef m_Carrier;
SGGeod m_geod;
bool m_offsetEnabled = false;
bool m_abeam = false;
QuantityValue m_offsetDistance;
};
#endif // of GUI_CARRIER_DIAGRAM_HXX

View file

@ -12,7 +12,7 @@ CarriersLocationModel::CarriersLocationModel(QObject *parent)
SGPropertyNode_ptr localRoot(new SGPropertyNode); SGPropertyNode_ptr localRoot(new SGPropertyNode);
FGAIManager::registerScenarios(localRoot); FGAIManager::registerScenarios(localRoot);
// this code encodes some scenario structre, sorry // this code encodes some scenario structure, sorry
for (auto s : localRoot->getNode("sim/ai/scenarios")->getChildren("scenario")) { for (auto s : localRoot->getNode("sim/ai/scenarios")->getChildren("scenario")) {
const std::string scenarioId = s->getStringValue("id"); const std::string scenarioId = s->getStringValue("id");
for (auto c : s->getChildren("carrier")) { for (auto c : s->getChildren("carrier")) {
@ -26,6 +26,7 @@ void CarriersLocationModel::processCarrier(const string &scenario, SGPropertyNod
const auto name = QString::fromStdString(carrierNode->getStringValue("name")); const auto name = QString::fromStdString(carrierNode->getStringValue("name"));
const auto pennant = QString::fromStdString(carrierNode->getStringValue("pennant-number")); const auto pennant = QString::fromStdString(carrierNode->getStringValue("pennant-number"));
const auto tacan = QString::fromStdString(carrierNode->getStringValue("TACAN-channel-ID")); const auto tacan = QString::fromStdString(carrierNode->getStringValue("TACAN-channel-ID"));
const auto desc = QString::fromStdString(carrierNode->getStringValue("description"));
SGGeod geod = SGGeod::fromDeg(carrierNode->getDoubleValue("longitude"), SGGeod geod = SGGeod::fromDeg(carrierNode->getDoubleValue("longitude"),
carrierNode->getDoubleValue("latitude")); carrierNode->getDoubleValue("latitude"));
@ -38,6 +39,7 @@ void CarriersLocationModel::processCarrier(const string &scenario, SGPropertyNod
QString::fromStdString(scenario), QString::fromStdString(scenario),
pennant, pennant,
name, name,
desc,
geod, geod,
tacan, tacan,
parkings parkings
@ -65,6 +67,8 @@ QVariant CarriersLocationModel::data(const QModelIndex &index, int role) const
case NameRole: return c.mName; case NameRole: return c.mName;
// case GeodRole: return QVariant::fromValue(c.mInitialLocation); // case GeodRole: return QVariant::fromValue(c.mInitialLocation);
case IdentRole: return c.mCallsign; case IdentRole: return c.mCallsign;
case DescriptionRole: return c.mDescription;
case TypeRole: return "Carrier";
case IconRole: return QPixmap(":/svg/aircraft-carrier"); case IconRole: return QPixmap(":/svg/aircraft-carrier");
default: default:
break; break;
@ -83,6 +87,7 @@ QHash<int, QByteArray> CarriersLocationModel::roleNames() const
result[NameRole] = "name"; result[NameRole] = "name";
result[IconRole] = "icon"; result[IconRole] = "icon";
result[TypeRole] = "type"; result[TypeRole] = "type";
result[DescriptionRole] = "description";
result[NavFrequencyRole] = "frequency"; result[NavFrequencyRole] = "frequency";
return result; return result;
} }

View file

@ -30,7 +30,8 @@ public:
NameRole = Qt::UserRole + 4, NameRole = Qt::UserRole + 4,
IconRole = Qt::UserRole + 5, IconRole = Qt::UserRole + 5,
TypeRole = Qt::UserRole + 6, TypeRole = Qt::UserRole + 6,
NavFrequencyRole = Qt::UserRole + 7 NavFrequencyRole = Qt::UserRole + 7,
DescriptionRole = Qt::UserRole + 8
}; };
int indexOf(const QString name) const; int indexOf(const QString name) const;
@ -46,6 +47,7 @@ private:
QString mScenario; // scenario ID for loading QString mScenario; // scenario ID for loading
QString mCallsign; // pennant-number QString mCallsign; // pennant-number
QString mName; QString mName;
QString mDescription;
SGGeod mInitialLocation; SGGeod mInitialLocation;
// icon? // icon?
QString mTACAN; QString mTACAN;

View file

@ -111,7 +111,7 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
desc = tr("The catalog data was not found on the server at the expected location (URL)"); desc = tr("The catalog data was not found on the server at the expected location (URL)");
break; break;
case Delegate::FAIL_VERSION: case Delegate::FAIL_VERSION:
desc = tr("The catalog is not comaptible with the version of FlightGear"); desc = tr("The catalog is not compatible with the version of FlightGear");
break; break;
case Delegate::FAIL_HTTP_FORBIDDEN: case Delegate::FAIL_HTTP_FORBIDDEN:
desc = tr("The catalog server is blocking access from some reason (forbidden)"); desc = tr("The catalog server is blocking access from some reason (forbidden)");

View file

@ -0,0 +1,62 @@
#include "FavouriteAircraftData.hxx"
#include <QSettings>
#include <memory>
static std::unique_ptr<FavouriteAircraftData> static_instance;
FavouriteAircraftData *FavouriteAircraftData::instance()
{
if (!static_instance) {
static_instance.reset(new FavouriteAircraftData);
}
return static_instance.get();
}
bool FavouriteAircraftData::isFavourite(QUrl u) const
{
return m_favourites.contains(u);
}
bool FavouriteAircraftData::setFavourite(QUrl u, bool b)
{
const auto cur = m_favourites.contains(u);
if (b == cur)
return false;
if (b && !cur) {
m_favourites.append(u);
} else if (!b && cur) {
m_favourites.removeOne(u);
}
emit changed(u);
saveFavourites();
return true;
}
FavouriteAircraftData::FavouriteAircraftData()
{
loadFavourites();
}
void FavouriteAircraftData::loadFavourites()
{
m_favourites.clear();
QSettings settings;
Q_FOREACH(auto v, settings.value("favourite-aircraft").toList()) {
m_favourites.append(v.toUrl());
}
}
void FavouriteAircraftData::saveFavourites()
{
QVariantList favs;
Q_FOREACH(auto u, m_favourites) {
favs.append(u);
}
QSettings settings;
settings.setValue("favourite-aircraft", favs);
}

View file

@ -0,0 +1,31 @@
#ifndef FAVOURITEAIRCRAFTDATA_HXX
#define FAVOURITEAIRCRAFTDATA_HXX
#include <QObject>
#include <QUrl>
#include <QVector>
class FavouriteAircraftData : public QObject
{
Q_OBJECT
public:
static FavouriteAircraftData* instance();
bool isFavourite(QUrl u) const;
bool setFavourite(QUrl u, bool b);
signals:
void changed(QUrl u);
private:
FavouriteAircraftData();
void loadFavourites();
void saveFavourites();
QVector<QUrl> m_favourites;
};
#endif // FAVOURITEAIRCRAFTDATA_HXX

View file

@ -45,6 +45,7 @@
#include "QmlPositioned.hxx" #include "QmlPositioned.hxx"
#include "PixmapImageItem.hxx" #include "PixmapImageItem.hxx"
#include "AirportDiagram.hxx" #include "AirportDiagram.hxx"
#include "CarrierDiagram.hxx"
#include "NavaidDiagram.hxx" #include "NavaidDiagram.hxx"
#include "RouteDiagram.hxx" #include "RouteDiagram.hxx"
#include "QmlRadioButtonHelper.hxx" #include "QmlRadioButtonHelper.hxx"
@ -168,6 +169,7 @@ void LauncherController::initQML()
qmlRegisterType<PixmapImageItem>("FlightGear", 1, 0, "PixmapImage"); qmlRegisterType<PixmapImageItem>("FlightGear", 1, 0, "PixmapImage");
qmlRegisterType<AirportDiagram>("FlightGear", 1, 0, "AirportDiagram"); qmlRegisterType<AirportDiagram>("FlightGear", 1, 0, "AirportDiagram");
qmlRegisterType<CarrierDiagram>("FlightGear", 1, 0, "CarrierDiagram");
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram"); qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram"); qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram");
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup"); qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
@ -389,6 +391,10 @@ QString LauncherController::selectAircraftStateAutomatically()
} }
} }
if (m_location->isCarrier() && m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("carrier-approach")) {
return "carrier-approach";
}
if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("approach")) { if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("approach")) {
return "approach"; return "approach";
} }
@ -400,10 +406,14 @@ QString LauncherController::selectAircraftStateAutomatically()
if (m_selectedAircraftInfo->hasState("parking")) { if (m_selectedAircraftInfo->hasState("parking")) {
return "parking"; return "parking";
} }
} else { }
// also try 'engines-running'?
if (m_selectedAircraftInfo->hasState("take-off")) if (m_location->isCarrier() && m_selectedAircraftInfo->hasState("carrier-take-off")) {
return "take-off"; return "carrier-take-off";
}
if (m_selectedAircraftInfo->hasState("take-off")) {
return "take-off";
} }
return {}; // failed to compute, give up return {}; // failed to compute, give up

View file

@ -39,9 +39,6 @@
static quint32 CACHE_VERSION = 12; static quint32 CACHE_VERSION = 12;
const int STANDARD_THUMBNAIL_HEIGHT = 128;
//const int STANDARD_THUMBNAIL_WIDTH = 172;
AircraftItem::AircraftItem() AircraftItem::AircraftItem()
{ {
} }

View file

@ -31,6 +31,7 @@
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
#include "AirportDiagram.hxx" #include "AirportDiagram.hxx"
#include "CarrierDiagram.hxx"
#include "NavaidDiagram.hxx" #include "NavaidDiagram.hxx"
#include "LaunchConfig.hxx" #include "LaunchConfig.hxx"
#include "DefaultAircraftLocator.hxx" #include "DefaultAircraftLocator.hxx"
@ -226,6 +227,7 @@ void LocationController::clearLocation()
{ {
m_locationIsLatLon = false; m_locationIsLatLon = false;
m_locationIsCarrier = false; m_locationIsCarrier = false;
m_abeam = false;
m_location.clear(); m_location.clear();
m_carrierName.clear(); m_carrierName.clear();
m_airportLocation.clear(); m_airportLocation.clear();
@ -365,8 +367,8 @@ void LocationController::showHistoryInSearchModel()
const std::string tutorialICAO = "PHTO"; // C172P tutorial aiurport const std::string tutorialICAO = "PHTO"; // C172P tutorial aiurport
// remove them from the recent locations // remove them from the recent locations
auto it = std::remove_if(locs.begin(), locs.end(), auto it = std::remove_if(locs.begin(), locs.end(),
[defaultICAO, tutorialICAO](FGPositionedRef pos) [defaultICAO, tutorialICAO](FGPositionedRef pos)
{ {
return (pos->ident() == defaultICAO) || (pos->ident() == tutorialICAO); return (pos->ident() == defaultICAO) || (pos->ident() == tutorialICAO);
}); });
@ -507,6 +509,12 @@ void LocationController::setUseCarrierFLOLS(bool useCarrierFLOLS)
emit configChanged(); emit configChanged();
} }
void LocationController::setAbeam(bool abeam)
{
m_abeam = abeam;
emit configChanged();
}
void LocationController::restoreLocation(QVariantMap l) void LocationController::restoreLocation(QVariantMap l)
{ {
clearLocation(); clearLocation();
@ -533,6 +541,7 @@ void LocationController::restoreLocation(QVariantMap l)
setCarrierLocation(l.value("carrier").toString()); setCarrierLocation(l.value("carrier").toString());
if (l.contains("carrier-flols")) { if (l.contains("carrier-flols")) {
setUseCarrierFLOLS(l.value("carrier-flols").toBool()); setUseCarrierFLOLS(l.value("carrier-flols").toBool());
setAbeam(l.value("abeam").toBool());
// overwrite value form above, intentionally // overwrite value form above, intentionally
m_offsetDistance = l.value("location-carrier-flols-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>(); m_offsetDistance = l.value("location-carrier-flols-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
} else if (l.contains("carrier-parking")) { } else if (l.contains("carrier-parking")) {
@ -576,6 +585,7 @@ void LocationController::restoreLocation(QVariantMap l)
} }
m_onFinal = l.value("location-on-final").toBool(); m_onFinal = l.value("location-on-final").toBool();
setAbeam(l.value("abeam").toBool());
m_offsetDistance = l.value("location-apt-final-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>(); m_offsetDistance = l.value("location-apt-final-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
} // of location is an airport } // of location is an airport
} catch (const sg_exception&) { } catch (const sg_exception&) {
@ -593,7 +603,7 @@ bool LocationController::shouldStartPaused() const
if (m_useCarrierFLOLS) { if (m_useCarrierFLOLS) {
return true; return true;
} }
if (!m_location) { if (!m_location) {
return false; // defaults to on-ground at the default airport return false; // defaults to on-ground at the default airport
} }
@ -619,6 +629,7 @@ QVariantMap LocationController::saveLocation() const
if (m_useCarrierFLOLS) { if (m_useCarrierFLOLS) {
locationSet.insert("carrier-flols", true); locationSet.insert("carrier-flols", true);
locationSet.insert("location-carrier-flols-distance", QVariant::fromValue(m_offsetDistance)); locationSet.insert("location-carrier-flols-distance", QVariant::fromValue(m_offsetDistance));
locationSet.insert("abeam", m_abeam);
} else if (!m_carrierParking.isEmpty()) { } else if (!m_carrierParking.isEmpty()) {
locationSet.insert("carrier-parking", m_carrierParking); locationSet.insert("carrier-parking", m_carrierParking);
} }
@ -628,6 +639,7 @@ QVariantMap LocationController::saveLocation() const
if (m_airportLocation) { if (m_airportLocation) {
locationSet.insert("location-on-final", m_onFinal); locationSet.insert("location-on-final", m_onFinal);
locationSet.insert("location-apt-final-distance", QVariant::fromValue(m_offsetDistance)); locationSet.insert("location-apt-final-distance", QVariant::fromValue(m_offsetDistance));
locationSet.insert("abeam", m_abeam);
if (m_useActiveRunway) { if (m_useActiveRunway) {
locationSet.insert("location-apt-runway", "ACTIVE"); locationSet.insert("location-apt-runway", "ACTIVE");
} else if (m_useAvailableParking) { } else if (m_useAvailableParking) {
@ -675,7 +687,7 @@ void LocationController::setLocationProperties()
"runway-requested" << "navaid-id" << "offset-azimuth-deg" << "runway-requested" << "navaid-id" << "offset-azimuth-deg" <<
"offset-distance-nm" << "glideslope-deg" << "offset-distance-nm" << "glideslope-deg" <<
"speed-set" << "on-ground" << "airspeed-kt" << "speed-set" << "on-ground" << "airspeed-kt" <<
"airport-id" << "runway" << "parkpos" << "carrier"; "airport-id" << "runway" << "parkpos" << "carrier" << "abeam";
Q_FOREACH(QString s, props) { Q_FOREACH(QString s, props) {
SGPropertyNode* c = presets->getChild(s.toStdString()); SGPropertyNode* c = presets->getChild(s.toStdString());
@ -709,7 +721,8 @@ void LocationController::setLocationProperties()
// treat the FLOLS as a runway, for the purposes of communication with position-init // treat the FLOLS as a runway, for the purposes of communication with position-init
fgSetString("/sim/presets/runway", "FLOLS"); fgSetString("/sim/presets/runway", "FLOLS");
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value); fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
fgSetBool("/sim/presets/abeam", m_abeam);
applyAltitude();
applyAirspeed(); applyAirspeed();
} else if (!m_carrierParking.isEmpty()) { } else if (!m_carrierParking.isEmpty()) {
fgSetString("/sim/presets/parkpos", m_carrierParking.toStdString()); fgSetString("/sim/presets/parkpos", m_carrierParking.toStdString());
@ -730,6 +743,7 @@ void LocationController::setLocationProperties()
fgSetString("/sim/presets/airport-id", m_airportLocation->ident()); fgSetString("/sim/presets/airport-id", m_airportLocation->ident());
fgSetBool("/sim/presets/on-ground", true); fgSetBool("/sim/presets/on-ground", true);
fgSetBool("/sim/presets/airport-requested", true); fgSetBool("/sim/presets/airport-requested", true);
fgSetBool("/sim/presets/abeam", m_abeam);
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)); const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING)); const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING));
@ -792,11 +806,11 @@ void LocationController::setLocationProperties()
default: default:
break; break;
} }
// set disambiguation property // set disambiguation property
globals->get_props()->setIntValue("/sim/presets/navaid-id", globals->get_props()->setIntValue("/sim/presets/navaid-id",
static_cast<int>(m_location->guid())); static_cast<int>(m_location->guid()));
applyPositionOffset(); applyPositionOffset();
applyAltitude(); applyAltitude();
applyAirspeed(); applyAirspeed();
@ -847,7 +861,7 @@ void LocationController::applyAltitude()
switch (m_altitude.unit) { switch (m_altitude.unit) {
default: default:
qWarning() << Q_FUNC_INFO << "unsupported altitdue unit"; qWarning() << Q_FUNC_INFO << "unsupported altitude unit";
break; break;
case Units::FeetMSL: case Units::FeetMSL:
m_config->setArg("altitude", QString::number(m_altitude.value)); m_config->setArg("altitude", QString::number(m_altitude.value));
@ -915,6 +929,9 @@ void LocationController::onCollectConfig()
m_config->setArg("runway", QStringLiteral("FLOLS")); m_config->setArg("runway", QStringLiteral("FLOLS"));
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value; const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm)); m_config->setArg("offset-distance", QString::number(offsetNm));
if (m_abeam) m_config->setArg("carrier-abeam", QStringLiteral("true"));
applyAltitude();
applyAirspeed(); applyAirspeed();
} }
@ -1029,6 +1046,8 @@ QString compassPointFromHeading(int heading)
QString LocationController::description() const QString LocationController::description() const
{ {
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
if (!m_location) { if (!m_location) {
if (m_locationIsLatLon) { if (m_locationIsLatLon) {
const auto s = simgear::strutils::formatGeodAsString(m_geodLocation, const auto s = simgear::strutils::formatGeodAsString(m_geodLocation,
@ -1039,7 +1058,15 @@ QString LocationController::description() const
if (m_locationIsCarrier) { if (m_locationIsCarrier) {
QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName)); QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName));
return tr("on carrier %1 (%2)").arg(m_carrierName).arg(pennant); QString locationToCarrier;
if (m_abeam) {
locationToCarrier = tr("%1nm abeam").arg(offsetNm);
} else if (m_useCarrierFLOLS) {
locationToCarrier = tr("on %1nm final to").arg(offsetNm);
} else {
locationToCarrier = tr("on deck at %1 on").arg(m_carrierParking);
}
return tr("%1 carrier %2 (%3)").arg(locationToCarrier).arg(m_carrierName).arg(pennant);
} }
return tr("No location selected"); return tr("No location selected");
@ -1049,7 +1076,6 @@ QString LocationController::description() const
name = QString::fromStdString(m_location->name()); name = QString::fromStdString(m_location->name());
name = fixNavaidName(name); name = fixNavaidName(name);
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
if (m_airportLocation) { if (m_airportLocation) {
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)); const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));

View file

@ -75,6 +75,7 @@ class LocationController : public QObject
Q_PROPERTY(QStringList carrierParkings READ carrierParkings NOTIFY baseLocationChanged) Q_PROPERTY(QStringList carrierParkings READ carrierParkings NOTIFY baseLocationChanged)
Q_PROPERTY(bool useCarrierFLOLS READ useCarrierFLOLS WRITE setUseCarrierFLOLS NOTIFY configChanged) Q_PROPERTY(bool useCarrierFLOLS READ useCarrierFLOLS WRITE setUseCarrierFLOLS NOTIFY configChanged)
Q_PROPERTY(QString carrierParking READ carrierParking WRITE setCarrierParking NOTIFY configChanged) Q_PROPERTY(QString carrierParking READ carrierParking WRITE setCarrierParking NOTIFY configChanged)
Q_PROPERTY(bool abeam READ abeam WRITE setAbeam NOTIFY configChanged)
// allow collecting the location properties to be disabled, if the // allow collecting the location properties to be disabled, if the
@ -189,6 +190,11 @@ public:
return m_useCarrierFLOLS; return m_useCarrierFLOLS;
} }
bool abeam() const
{
return m_abeam;
}
public slots: public slots:
void setOffsetRadial(QuantityValue offsetRadial); void setOffsetRadial(QuantityValue offsetRadial);
@ -204,6 +210,8 @@ public slots:
void setUseCarrierFLOLS(bool useCarrierFLOLS); void setUseCarrierFLOLS(bool useCarrierFLOLS);
void setAbeam(bool abeam);
Q_SIGNALS: Q_SIGNALS:
void descriptionChanged(); void descriptionChanged();
void offsetChanged(); void offsetChanged();
@ -261,6 +269,7 @@ private:
bool m_speedEnabled = false; bool m_speedEnabled = false;
bool m_altitudeEnabled = false; bool m_altitudeEnabled = false;
bool m_skipFromArgs = false; bool m_skipFromArgs = false;
bool m_abeam;
bool m_useCarrierFLOLS = false; bool m_useCarrierFLOLS = false;
QString m_carrierParking; QString m_carrierParking;

View file

@ -43,7 +43,10 @@ QVariant ModelDataExtractor::data() const
return m_rawModel.property(uIndex).toVariant(); return m_rawModel.property(uIndex).toVariant();
} }
qWarning() << "Unable to convert model data:" << m_rawModel.toString(); if (!m_rawModel.isUndefined() && !m_rawModel.isNull()) {
qWarning() << "Unable to convert model data:" << m_rawModel.toString();
}
return {}; return {};
} }

View file

@ -14,6 +14,7 @@
#include <Main/globals.hxx> #include <Main/globals.hxx>
#include "LocalAircraftCache.hxx" #include "LocalAircraftCache.hxx"
#include "FavouriteAircraftData.hxx"
using namespace simgear::pkg; using namespace simgear::pkg;
@ -36,6 +37,8 @@ public:
} }
protected: protected:
void finishInstall(InstallRef aInstall, StatusCode aReason) override;
void catalogRefreshed(CatalogRef, StatusCode) override void catalogRefreshed(CatalogRef, StatusCode) override
{ {
} }
@ -55,19 +58,9 @@ protected:
} }
} }
void finishInstall(InstallRef aInstall, StatusCode aReason) override
{
Q_UNUSED(aReason);
if (aInstall->package() == p->packageRef()) {
p->_cachedProps.reset();
p->checkForStates();
p->infoChanged();
}
}
void installStatusChanged(InstallRef aInstall, StatusCode aReason) override void installStatusChanged(InstallRef aInstall, StatusCode aReason) override
{ {
Q_UNUSED(aReason); Q_UNUSED(aReason)
if (aInstall->package() == p->packageRef()) { if (aInstall->package() == p->packageRef()) {
p->downloadChanged(); p->downloadChanged();
} }
@ -85,6 +78,16 @@ private:
QmlAircraftInfo* p; QmlAircraftInfo* p;
}; };
void QmlAircraftInfo::Delegate::finishInstall(InstallRef aInstall, StatusCode aReason)
{
Q_UNUSED(aReason)
if (aInstall->package() == p->packageRef()) {
p->_cachedProps.reset();
p->checkForStates();
p->infoChanged();
}
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@ -109,7 +112,7 @@ static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
// malformed include or XML, just bail // malformed include or XML, just bail
return {}; return {};
} }
if (!root->getNode("sim/state")) { if (!root->getNode("sim/state")) {
return {}; return {};
} }
@ -141,7 +144,7 @@ QString humanNameFromStateTag(const std::string& tag)
{ {
if (tag == "approach") return QObject::tr("On approach"); if (tag == "approach") return QObject::tr("On approach");
if ((tag == "take-off") || (tag == "takeoff")) if ((tag == "take-off") || (tag == "takeoff"))
return QObject::tr("Ready for Take-off"); return QObject::tr("Ready for take-off");
if ((tag == "parked") || (tag == "parking") || (tag == "cold-and-dark")) if ((tag == "parked") || (tag == "parking") || (tag == "cold-and-dark"))
return QObject::tr("Parked, cold & dark"); return QObject::tr("Parked, cold & dark");
if (tag == "auto") if (tag == "auto")
@ -150,6 +153,10 @@ QString humanNameFromStateTag(const std::string& tag)
return QObject::tr("Cruise"); return QObject::tr("Cruise");
if (tag == "taxi") if (tag == "taxi")
return QObject::tr("Ready to taxi"); return QObject::tr("Ready to taxi");
if (tag == "carrier-approach")
return QObject::tr("On approach to a carrier");
if (tag == "carrier-take-off")
return QObject::tr("Ready for catapult launch");
qWarning() << Q_FUNC_INFO << "add translation / string for" << QString::fromStdString(tag); qWarning() << Q_FUNC_INFO << "add translation / string for" << QString::fromStdString(tag);
// no mapping, let's use the tag directly // no mapping, let's use the tag directly
@ -299,6 +306,8 @@ QmlAircraftInfo::QmlAircraftInfo(QObject *parent)
, _delegate(new Delegate(this)) , _delegate(new Delegate(this))
{ {
qmlRegisterUncreatableType<StatesModel>("FlightGear.Launcher", 1, 0, "StatesModel", "no"); qmlRegisterUncreatableType<StatesModel>("FlightGear.Launcher", 1, 0, "StatesModel", "no");
connect(FavouriteAircraftData::instance(), &FavouriteAircraftData::changed,
this, &QmlAircraftInfo::onFavouriteChanged);
} }
QmlAircraftInfo::~QmlAircraftInfo() QmlAircraftInfo::~QmlAircraftInfo()
@ -441,7 +450,7 @@ QVariantList QmlAircraftInfo::previews() const
for (auto p : previews) { for (auto p : previews) {
SGPath localPreviewPath = ex->path() / p.path; SGPath localPreviewPath = ex->path() / p.path;
if (!localPreviewPath.exists()) { if (!localPreviewPath.exists()) {
qWarning() << "missing local preview" << QString::fromStdString(localPreviewPath.utf8Str()); // this happens when the aircraft is being installed, for example
continue; continue;
} }
result.append(QUrl::fromLocalFile(QString::fromStdString(localPreviewPath.utf8Str()))); result.append(QUrl::fromLocalFile(QString::fromStdString(localPreviewPath.utf8Str())));
@ -667,6 +676,7 @@ void QmlAircraftInfo::setUri(QUrl u)
emit uriChanged(); emit uriChanged();
emit infoChanged(); emit infoChanged();
emit downloadChanged(); emit downloadChanged();
emit favouriteChanged();
} }
void QmlAircraftInfo::setVariant(quint32 variant) void QmlAircraftInfo::setVariant(quint32 variant)
@ -691,6 +701,19 @@ void QmlAircraftInfo::setVariant(quint32 variant)
emit variantChanged(_variant); emit variantChanged(_variant);
} }
void QmlAircraftInfo::setFavourite(bool favourite)
{
FavouriteAircraftData::instance()->setFavourite(uri(), favourite);
}
void QmlAircraftInfo::onFavouriteChanged(QUrl u)
{
if (u != uri())
return;
emit favouriteChanged();
}
QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p) QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p)
{ {
if (p->hasTag("needs-maintenance")) { if (p->hasTag("needs-maintenance")) {
@ -876,5 +899,9 @@ bool QmlAircraftInfo::hasTag(QString tag) const
return false; return false;
} }
#include "QmlAircraftInfo.moc" bool QmlAircraftInfo::favourite() const
{
return FavouriteAircraftData::instance()->isFavourite(uri());
}
#include "QmlAircraftInfo.moc"

View file

@ -59,6 +59,7 @@ class QmlAircraftInfo : public QObject
Q_PROPERTY(bool hasStates READ hasStates NOTIFY infoChanged) Q_PROPERTY(bool hasStates READ hasStates NOTIFY infoChanged)
Q_PROPERTY(StatesModel* statesModel READ statesModel NOTIFY infoChanged) Q_PROPERTY(StatesModel* statesModel READ statesModel NOTIFY infoChanged)
Q_PROPERTY(bool favourite READ favourite WRITE setFavourite NOTIFY favouriteChanged)
public: public:
explicit QmlAircraftInfo(QObject *parent = nullptr); explicit QmlAircraftInfo(QObject *parent = nullptr);
virtual ~QmlAircraftInfo(); virtual ~QmlAircraftInfo();
@ -127,11 +128,14 @@ public:
Q_INVOKABLE bool isAltitudeBelowLimits(QuantityValue speed) const; Q_INVOKABLE bool isAltitudeBelowLimits(QuantityValue speed) const;
Q_INVOKABLE bool hasTag(QString tag) const; Q_INVOKABLE bool hasTag(QString tag) const;
bool favourite() const;
signals: signals:
void uriChanged(); void uriChanged();
void infoChanged(); void infoChanged();
void downloadChanged(); void downloadChanged();
void variantChanged(quint32 variant); void variantChanged(quint32 variant);
void favouriteChanged();
public slots: public slots:
@ -139,6 +143,10 @@ public slots:
void setVariant(quint32 variant); void setVariant(quint32 variant);
void setFavourite(bool favourite);
private slots:
void onFavouriteChanged(QUrl u);
private: private:
AircraftItemPtr resolveItem() const; AircraftItemPtr resolveItem() const;
void checkForStates(); void checkForStates();

View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="87.149193"
height="37.111004"
viewBox="0 0 461.16447 196.37906"
version="1.1"
id="svg8"
inkscape:export-filename="/home/stuart/bitmap.png"
inkscape:export-xdpi="2.8"
inkscape:export-ydpi="2.8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="aircraft-carrier-icon.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="82.435073"
inkscape:cy="-17.019439"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2493"
inkscape:window-height="1385"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1"
fit-margin-top="5"
fit-margin-left="5"
fit-margin-right="5"
fit-margin-bottom="5"
units="px"
scale-x="10" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline"
transform="translate(319.16852,-98.581295)">
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#009bff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -290.00765,177.53052 7.30519,71.36096 55.25252,16.03618 215.076885,1.06907 30.407428,-31.5378 94.930487,-12.2944 -1.4833,-45.43582 -94.559656,-10.69078 -31.890713,-38.48681 -219.155941,2.13815 -8.15808,34.74504 z"
id="Outline"
inkscape:connector-curvature="0"
inkscape:label="#path826"
sodipodi:nodetypes="cccccccccccc" />
<path
style="fill:#009bff;fill-opacity:1;fill-rule:evenodd;stroke:#009bff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -82.210854,265.48522 0.200595,-46.99016 h -24.786801 l -0.73387,13.23009 -59.39478,-0.54281 -0.6013,33.61805 z"
id="Island"
inkscape:connector-curvature="0"
inkscape:label="#path828"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -286.7815,192.23034 4.44986,40.62497"
id="path832"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -14.968809,127.55112 -271.812691,64.67922 2.97333,48.86685 300.730074,-75.05926"
id="LandingMarks"
inkscape:connector-curvature="0"
inkscape:label="#path834"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -47.355536,198.09657 100.08338,193.60644"
id="Cat1"
inkscape:connector-curvature="0"
inkscape:label="#path836" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -44.09832,226.88876 102.04272,209.30242"
id="Cat2"
inkscape:connector-curvature="0"
inkscape:label="#path836"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:29.99999994, 4.99999999;stroke-dashoffset:0;stroke-opacity:1"
d="M -273.57231,212.56627 -18.148692,149.13964"
id="Centerline"
inkscape:connector-curvature="0"
inkscape:label="#path853"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -155,59 +155,71 @@ Rectangle {
visible: aircraft.previews.length > 0 visible: aircraft.previews.length > 0
} }
Grid { Row {
id: ratingGrid height: ratingGrid.height
anchors.left: parent.left width: parent.width
spacing: Style.strutSize
visible: aircraft.ratings !== undefined FavouriteToggleButton {
checked: aircraft.favourite
rows: 2 onToggle: {
columns: 3 aircraft.favourite = on;
rowSpacing: Style.margin
columnSpacing: Style.margin
StyledText {
id: ratingsLabel
text: qsTr("Ratings:")
}
AircraftRating {
title: qsTr("Flight model")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[0]
} }
} }
AircraftRating { Grid {
title: qsTr("Systems") id: ratingGrid
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[1]
}
}
Item { visible: aircraft.ratings !== undefined
width: ratingsLabel.width
height: 1
} // placeholder
AircraftRating { rows: 2
title: qsTr("Cockpit") columns: 3
Binding on value { rowSpacing: Style.margin
when: aircraft.ratings !== undefined columnSpacing: Style.margin
value: aircraft.ratings[2]
}
}
AircraftRating { StyledText {
title: qsTr("Exterior") id: ratingsLabel
Binding on value { text: qsTr("Ratings:")
when: aircraft.ratings !== undefined
value: aircraft.ratings[3]
} }
}
AircraftRating {
title: qsTr("Flight model")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[0]
}
}
AircraftRating {
title: qsTr("Systems")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[1]
}
}
Item {
width: ratingsLabel.width
height: 1
} // placeholder
AircraftRating {
title: qsTr("Cockpit")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[2]
}
}
AircraftRating {
title: qsTr("Exterior")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[3]
}
}
} // of rating grid
} }
StyledText { StyledText {

View file

@ -109,6 +109,10 @@ Item {
return "%1 - %2 (%3 MHz)".arg(model.ident).arg(model.name).arg(freq); return "%1 - %2 (%3 MHz)".arg(model.ident).arg(model.name).arg(freq);
} }
if (model.type === "Carrier") {
return "%1 - %2\n%3".arg(model.ident).arg(model.name).arg(model.description);
}
// general case // general case
return "%1 - %2".arg(model.ident).arg(model.name); return "%1 - %2".arg(model.ident).arg(model.name);
} }
@ -225,7 +229,13 @@ Item {
icon: "qrc:///svg/icon-carrier" icon: "qrc:///svg/icon-carrier"
onClicked: { onClicked: {
root.showCarriers = true; root.showCarriers = ! root.showCarriers;
if (root.showCarriers) {
this.icon = "qrc:///svg/icon-airport"
} else {
this.icon = "qrc:///svg/icon-carrier"
}
} }
} }

View file

@ -6,14 +6,13 @@ import "."
Item { Item {
property alias geod: diagram.geod property alias geod: diagram.geod
NavaidDiagram { CarrierDiagram {
id: diagram id: diagram
anchors.fill: parent anchors.fill: parent
offsetEnabled: _location.offsetEnabled offsetEnabled: _location.useCarrierFLOLS
offsetBearing: _location.offsetRadial
offsetDistance: _location.offsetDistance offsetDistance: _location.offsetDistance
heading: _location.heading abeam: _location.abeam
} }
Component.onCompleted: { Component.onCompleted: {
@ -22,7 +21,9 @@ Item {
function syncUIFromController() function syncUIFromController()
{ {
if (_location.useCarrierFLOLS) { if (_location.abeam) {
abeamRadio.select()
} else if (_location.useCarrierFLOLS) {
flolsRadio.select() flolsRadio.select()
} else { } else {
parkingRadio.select(); parkingRadio.select();
@ -46,7 +47,7 @@ Item {
height: selectionGrid.height + Style.margin * 2 height: selectionGrid.height + Style.margin * 2
// set opacity here only, so we don't make the whole summary pannel translucent // set opacity here only, so we don't make the whole summary panel translucent
Rectangle { Rectangle {
id: background id: background
anchors.fill: parent anchors.fill: parent
@ -76,7 +77,7 @@ Item {
font.pixelSize: Style.headingFontPixelSize font.pixelSize: Style.headingFontPixelSize
} }
// on FLOLS offset // on final approach
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Style.margin anchors.leftMargin: Style.margin
@ -89,9 +90,12 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
group: radioGroup group: radioGroup
onClicked: { onClicked: {
if (selected) _location.useCarrierFLOLS = selected if (selected) {
_location.useCarrierFLOLS = selected;
_location.abeam = false;
}
} }
selected: _location.useCarrierFLOLS selected: _location.useCarrierFLOLS && (!_location.abeam)
} }
StyledText { StyledText {
@ -101,6 +105,47 @@ Item {
} }
}
// Abeam the FLOLS
Row {
anchors.left: parent.left
anchors.leftMargin: Style.margin
anchors.right: parent.right
anchors.rightMargin: Style.margin
spacing: Style.margin
RadioButton {
id: abeamRadio
anchors.verticalCenter: parent.verticalCenter
group: radioGroup
onClicked: {
if (selected) _location.abeam = _location.useCarrierFLOLS = true
}
selected: _location.abeam
}
StyledText {
text: qsTr("Abeam carrier at 180 degrees")
anchors.verticalCenter: parent.verticalCenter
enabled: abeamRadio.selected
}
}
// Offset selection
readonly property bool offsetEnabled: (flolsRadio.selected || abeamRadio.selected)
Row {
anchors.left: parent.left
anchors.leftMargin: Style.margin
anchors.right: parent.right
anchors.rightMargin: Style.margin
spacing: Style.margin
Item {
height: 1; width: Style.strutSize
}
NumericalEdit { NumericalEdit {
id: offsetNmEdit id: offsetNmEdit
quantity: _location.offsetDistance quantity: _location.offsetDistance
@ -109,13 +154,13 @@ Item {
unitsMode: Units.Distance unitsMode: Units.Distance
live: true live: true
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
enabled: flolsRadio.selected enabled: selectionGrid.offsetEnabled
} }
StyledText { StyledText {
text: qsTr(" from the FLOLS (aka the ball)") text: qsTr(" from the FLOLS (aka the ball)")
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
enabled: flolsRadio.selected enabled: selectionGrid.offsetEnabled
} }
Item { Item {
@ -124,7 +169,7 @@ Item {
ToggleSwitch { ToggleSwitch {
id: airspeedToggle id: airspeedToggle
enabled: flolsRadio.selected enabled: selectionGrid.offsetEnabled
checked: _location.speedEnabled checked: _location.speedEnabled
onCheckedChanged: _location.speedEnabled = checked; onCheckedChanged: _location.speedEnabled = checked;
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -134,12 +179,23 @@ Item {
id: airspeedSpinbox id: airspeedSpinbox
label: qsTr("Airspeed:") label: qsTr("Airspeed:")
unitsMode: Units.SpeedWithoutMach unitsMode: Units.SpeedWithoutMach
enabled: _location.speedEnabled && flolsRadio.selected enabled: _location.speedEnabled && selectionGrid.offsetEnabled
quantity: _location.airspeed quantity: _location.airspeed
onCommit: _location.airspeed = newValue onCommit: _location.airspeed = newValue
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} // of FLOLS row
Item {
height: 1; width: Style.strutSize
}
LocationAltitudeRow
{
enabled: selectionGrid.offsetEnabled
width: parent.width
}
} // of Offset selection
// parking row // parking row
Row { Row {
@ -159,10 +215,11 @@ Item {
onClicked: { onClicked: {
if (selected) parkingChoice.setLocation(); if (selected) parkingChoice.setLocation();
} }
selected : (! _location.abeam) && (! _location.useCarrierFLOLS)
} }
StyledText { StyledText {
text: qsTr("Parking") text: qsTr("On deck")
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
enabled: parkingRadio.selected enabled: parkingRadio.selected
} }

View file

@ -90,8 +90,9 @@ Item {
var defaultValue = ("defaultValue" in root) ? root.defaultValue : undefined; var defaultValue = ("defaultValue" in root) ? root.defaultValue : undefined;
var rawValue = _config.getValueForKey("", root.setting, defaultValue); var rawValue = _config.getValueForKey("", root.setting, defaultValue);
// console.warn("restoring state for " + root.setting + ", got raw value " + rawValue + " with type " + typeof(rawValue))
if (rawValue !== undefined) { // console.warn("restoring state for " + root.setting + ", got raw value " + rawValue + " with type " + typeof(rawValue))
if (rawValue !== defaultValue) {
setValue(rawValue); setValue(rawValue);
} }
} }
@ -99,6 +100,6 @@ Item {
function setValue(newValue) function setValue(newValue)
{ {
// hook method so controls can override // hook method so controls can override
this.value = newValue root.value = newValue
} }
} }

View file

@ -155,6 +155,7 @@
<file alias="icon-list-view">assets/icons8-menu.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-hide">assets/icons8-hide-50.png</file>
<file alias="icon-carrier">assets/icons8-cargo-ship-50.png</file> <file alias="icon-carrier">assets/icons8-cargo-ship-50.png</file>
<file alias="aircraft-carrier">assets/aircraft-carrier-icon.png</file> <file alias="icon-airport">assets/icons8-airport-50.png</file>
<file alias="aircraft-carrier">assets/aircraft-carrier-icon.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -70,3 +70,5 @@
#cmakedefine ENABLE_COMPOSITOR #cmakedefine ENABLE_COMPOSITOR
#cmakedefine ENABLE_SWIFT #cmakedefine ENABLE_SWIFT
#cmakedefine HAVE_SENTRY

View file

@ -1,8 +1,7 @@
include(FlightGearComponent) include(FlightGearComponent)
IF(APPLE) IF(APPLE)
set(EVENT_INPUT_SOURCES FGMacOSXEventInput.cxx) # no Mac implemention, use HID
set(EVENT_INPUT_HEADERS FGMacOSXEventInput.hxx)
elseif(WIN32) elseif(WIN32)
# no Win32 specific implementation, at least for now # no Win32 specific implementation, at least for now
else() else()

View file

@ -176,12 +176,12 @@ void FGAxisEvent::fire( FGEventData & eventData )
// We need a copy of the FGEventData struct to set the new value and to avoid side effects // We need a copy of the FGEventData struct to set the new value and to avoid side effects
FGEventData ed = eventData; FGEventData ed = eventData;
if( fabs(ed.value) < deadband )
ed.value = 0.0;
if( minRange != maxRange ) if( minRange != maxRange )
ed.value = 2.0*(eventData.value-minRange)/(maxRange-minRange)-1.0; ed.value = 2.0*(eventData.value-minRange)/(maxRange-minRange)-1.0;
if( fabs(ed.value) < deadband )
ed.value = 0.0;
if (interpolater) { if (interpolater) {
if ((ed.value < 0.0) && mirrorInterpolater) { if ((ed.value < 0.0) && mirrorInterpolater) {
// mirror the positive interpolation for negative values // mirror the positive interpolation for negative values

View file

@ -312,12 +312,16 @@ private:
std::string _hidPath; std::string _hidPath;
hid_device* _device = nullptr; hid_device* _device = nullptr;
bool _haveNumberedReports = false; bool _haveNumberedReports = false;
bool _debugRaw = false;
/// set if we parsed the device description our XML /// set if we parsed the device description our XML
/// instead of from the USB data. Useful on Windows where the data /// instead of from the USB data. Useful on Windows where the data
/// is inaccessible, or devices with broken descriptors /// is inaccessible, or devices with broken descriptors
bool _haveLocalDescriptor = false; bool _haveLocalDescriptor = false;
/// allow specifying the descriptor as hex bytes in XML
std::vector<uint8_t>_rawXMLDescriptor;
// all sets which will be send on the next update() call. // all sets which will be send on the next update() call.
std::set<Report*> _dirtyReports; std::set<Report*> _dirtyReports;
}; };
@ -384,6 +388,17 @@ void FGHIDDevice::Configure(SGPropertyNode_ptr node)
defineReport(report); defineReport(report);
} }
} }
if (node->hasChild("hid-raw-descriptor")) {
_rawXMLDescriptor = simgear::strutils::decodeHex(node->getStringValue("hid-raw-descriptor"));
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using XML-defined raw HID descriptor");
}
}
if (node->getBoolValue("hid-debug-raw")) {
_debugRaw = true;
}
} }
bool FGHIDDevice::Open() bool FGHIDDevice::Open()
@ -395,6 +410,19 @@ bool FGHIDDevice::Open()
return false; return false;
} }
#if !defined(SG_WINDOWS)
if (_rawXMLDescriptor.empty()) {
_rawXMLDescriptor.resize(2048);
int descriptorSize = hid_get_descriptor(_device, _rawXMLDescriptor.data(), _rawXMLDescriptor.size());
if (descriptorSize <= 0) {
SG_LOG(SG_INPUT, SG_WARN, "HID: " << GetUniqueName() << " failed to read HID descriptor");
return false;
}
_rawXMLDescriptor.resize(descriptorSize);
}
#endif
if (!_haveLocalDescriptor) { if (!_haveLocalDescriptor) {
bool ok = parseUSBHIDDescriptor(); bool ok = parseUSBHIDDescriptor();
if (!ok) if (!ok)
@ -422,26 +450,22 @@ bool FGHIDDevice::Open()
bool FGHIDDevice::parseUSBHIDDescriptor() bool FGHIDDevice::parseUSBHIDDescriptor()
{ {
#if defined(SG_WINDOWS) #if defined(SG_WINDOWS)
SG_LOG(SG_INPUT, SG_ALERT, GetUniqueName() << ": on Windows, there is no way to extract the UDB-HID report descriptor. " if (_rawXMLDescriptor.empty()) {
<< "\nPlease supply the report descriptor in the device XML configuration."); SG_LOG(SG_INPUT, SG_ALERT, GetUniqueName() << ": on Windows, there is no way to extract the UDB-HID report descriptor. "
return false; << "\nPlease supply the report descriptor in the device XML configuration.");
#endif SG_LOG(SG_INPUT, SG_ALERT, "See this page:<> for information on extracting the report descriptor on Windows");
unsigned char reportDescriptor[1024];
int descriptorSize = hid_get_descriptor(_device, reportDescriptor, 1024);
if (descriptorSize <= 0) {
SG_LOG(SG_INPUT, SG_WARN, "HID: " << GetUniqueName() << " failed to read HID descriptor");
return false; return false;
} }
#endif
if (debugEvents) { if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, "\nHID: descriptor for:" << GetUniqueName()); SG_LOG(SG_INPUT, SG_INFO, "\nHID: descriptor for:" << GetUniqueName());
{ {
std::ostringstream byteString; std::ostringstream byteString;
for (int i=0; i<descriptorSize; ++i) { for (auto i=0; i<_rawXMLDescriptor.size(); ++i) {
byteString << hexTable[reportDescriptor[i] >> 4]; byteString << hexTable[_rawXMLDescriptor[i] >> 4];
byteString << hexTable[reportDescriptor[i] & 0x0f]; byteString << hexTable[_rawXMLDescriptor[i] & 0x0f];
byteString << " "; byteString << " ";
} }
SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str()); SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
@ -449,7 +473,7 @@ bool FGHIDDevice::parseUSBHIDDescriptor()
} }
hid_item* rootItem = nullptr; hid_item* rootItem = nullptr;
hid_parse_reportdesc(reportDescriptor, descriptorSize, &rootItem); hid_parse_reportdesc(_rawXMLDescriptor.data(), _rawXMLDescriptor.size(), &rootItem);
if (debugEvents) { if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, "\nHID: scan for:" << GetUniqueName()); SG_LOG(SG_INPUT, SG_INFO, "\nHID: scan for:" << GetUniqueName());
} }
@ -645,6 +669,18 @@ void FGHIDDevice::sendReport(Report* report) const
} }
reportLength /= 8; reportLength /= 8;
if (_debugRaw) {
std::ostringstream byteString;
for (size_t i=0; i<reportLength; ++i) {
byteString << hexTable[reportBytes[i] >> 4];
byteString << hexTable[reportBytes[i] & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "sending bytes: " << byteString.str());
}
// send the data, based on the report type // send the data, based on the report type
if (report->type == HID::ReportType::Feature) { if (report->type == HID::ReportType::Feature) {
hid_send_feature_report(_device, reportBytes, reportLength + 1); hid_send_feature_report(_device, reportBytes, reportLength + 1);
@ -663,7 +699,7 @@ void FGHIDDevice::processInputReport(Report* report, unsigned char* data,
size_t length, size_t length,
double dt, int keyModifiers) double dt, int keyModifiers)
{ {
if (debugEvents) { if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, GetName() << " FGHIDDeivce received input report:" << (int) report->number << ", len=" << length); SG_LOG(SG_INPUT, SG_INFO, GetName() << " FGHIDDeivce received input report:" << (int) report->number << ", len=" << length);
{ {
std::ostringstream byteString; std::ostringstream byteString;
@ -698,7 +734,7 @@ void FGHIDDevice::processInputReport(Report* report, unsigned char* data,
if (!item->event) if (!item->event)
continue; continue;
if (debugEvents) { if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, "\titem:" << item->name << " = " << value); SG_LOG(SG_INPUT, SG_INFO, "\titem:" << item->name << " = " << value);
} }
@ -713,7 +749,7 @@ void FGHIDDevice::SendFeatureReport(unsigned int reportId, const std::string& da
return; return;
} }
if (debugEvents) { if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, GetName() << ": FGHIDDevice: Sending feature report:" << (int) reportId << ", len=" << data.size()); SG_LOG(SG_INPUT, SG_INFO, GetName() << ": FGHIDDevice: Sending feature report:" << (int) reportId << ", len=" << data.size());
{ {
std::ostringstream byteString; std::ostringstream byteString;

View file

@ -1,650 +0,0 @@
// FGMacOSXEventInput.cxx -- handle event driven input devices for Mac OS X
//
// Written by Tatsuhiro Nishioka, started Aug. 2009.
//
// Copyright (C) 2009 Tasuhiro Nishioka, tat <dot> fgmacosx <at> gmail <dot> com
//
// 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.
//
// $Id$
#include "FGMacOSXEventInput.hxx"
#include <simgear/sg_inlines.h>
#include <cstdlib>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/HID/IOHIDKeys.h>
#include <simgear/structure/exception.hxx>
static std::string nameForUsage(uint32_t usagePage, uint32_t usage)
{
if (usagePage == kHIDPage_GenericDesktop) {
switch (usage) {
case kHIDUsage_GD_Joystick: return "joystick";
case kHIDUsage_GD_Wheel: return "wheel";
case kHIDUsage_GD_Dial: return "dial";
case kHIDUsage_GD_Hatswitch: return "hat";
case kHIDUsage_GD_Slider: return "slider";
case kHIDUsage_GD_Rx: return "x-rotate";
case kHIDUsage_GD_Ry: return "y-rotate";
case kHIDUsage_GD_Rz: return "z-rotate";
case kHIDUsage_GD_X: return "x-translate";
case kHIDUsage_GD_Y: return "y-translate";
case kHIDUsage_GD_Z: return "z-translate";
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID generic desktop usage:" << usage);
}
} else if (usagePage == kHIDPage_Simulation) {
switch (usage) {
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID simulation usage:" << usage);
}
} else if (usagePage == kHIDPage_Consumer) {
switch (usage) {
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID consumer usage:" << usage);
}
} else if (usagePage == kHIDPage_AlphanumericDisplay) {
switch (usage) {
case kHIDUsage_AD_AlphanumericDisplay: return "alphanumeric";
case kHIDUsage_AD_CharacterReport: return "character-report";
case kHIDUsage_AD_DisplayData: return "display-data";
case 0x46: return "display-brightness";
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID alphanumeric usage:" << usage);
}
} else if (usagePage == kHIDPage_LEDs) {
switch (usage) {
case kHIDUsage_LED_GenericIndicator: return "led-misc";
case kHIDUsage_LED_Pause: return "led-pause";
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID LED usage:" << usage);
}
} else if (usagePage == kHIDPage_Button) {
std::stringstream os;
os << "button-" << usage;
return os.str();
} else if (usagePage >= kHIDPage_VendorDefinedStart) {
return "vendor";
} else {
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID usage page:" << usagePage);
}
return "unknown";
}
class FGMacOSXEventInputPrivate
{
public:
IOHIDManagerRef hidManager = nullptr;
FGMacOSXEventInput* p = nullptr;
double currentDt = 0.0;
int currentModifiers = 0;
void matchedDevice(IOHIDDeviceRef device);
void removedDevice(IOHIDDeviceRef device);
void iterateDevices(CFSetRef matchingSet);
std::string getDeviceStringProperty(IOHIDDeviceRef device, CFStringRef hidProp);
bool getDeviceIntProperty(IOHIDDeviceRef device, CFStringRef hidProp, int& value);
};
static void deviceMatchingCallback(
void * inContext, // context from IOHIDManagerRegisterDeviceMatchingCallback
IOReturn inResult, // the result of the matching operation
void * inSender, // the IOHIDManagerRef for the new device
IOHIDDeviceRef inIOHIDDeviceRef // the new HID device
)
{
// printf("%s(context: %p, result: %p, sender: %p, device: %p).\n",
// __PRETTY_FUNCTION__, inContext, (void *) inResult, inSender, (void*) inIOHIDDeviceRef);
FGMacOSXEventInputPrivate* p = static_cast<FGMacOSXEventInputPrivate*>(inContext);
p->matchedDevice(inIOHIDDeviceRef);
} // Handle_DeviceMatchingCallback
static void deviceRemovalCallback(
void * inContext, // context from IOHIDManagerRegisterDeviceMatchingCallback
IOReturn inResult, // the result of the matching operation
void * inSender, // the IOHIDManagerRef for the new device
IOHIDDeviceRef inIOHIDDeviceRef // the new HID device
)
{
// printf("%s(context: %p, result: %p, sender: %p, device: %p).\n",
// __PRETTY_FUNCTION__, inContext, (void *) inResult, inSender, (void*) inIOHIDDeviceRef);
FGMacOSXEventInputPrivate* p = static_cast<FGMacOSXEventInputPrivate*>(inContext);
p->removedDevice(inIOHIDDeviceRef);
} // Handle_DeviceMatchingCallback
//
// FGMacOSXInputDevice implementation
//
//
// FGMacOSXInputDevice
// Mac OS X specific FGInputDevice
//
class FGMacOSXInputDevice : public FGInputDevice {
public:
FGMacOSXInputDevice(IOHIDDeviceRef hidRef,
FGMacOSXEventInputPrivate* subsys);
virtual ~FGMacOSXInputDevice();
bool Open() override;
void Close() override;
virtual void update(double dt);
virtual const char *TranslateEventName(FGEventData &eventData);
virtual void Send( const char * eventName, double value );
virtual void SendFeatureReport(unsigned int reportId, const std::string& data);
virtual void AddHandledEvent( FGInputEvent_ptr handledEvent );
void drainQueue();
void handleValue(IOHIDValueRef value);
private:
void buildElementNameDictionary();
std::string nameForHIDElement(IOHIDElementRef element) const;
IOHIDDeviceRef _hid = nullptr;
IOHIDQueueRef _queue = nullptr;
FGMacOSXEventInputPrivate* _subsystem;
typedef std::map<std::string, IOHIDElementRef> NameElementDict;
NameElementDict namedElements;
};
#if 0
static void valueAvailableCallback(void * inContext, // context from IOHIDQueueRegisterValueAvailableCallback
IOReturn inResult, // the inResult
void * inSender // IOHIDQueueRef of the queue
) {
FGMacOSXInputDevice* dev = static_cast<FGMacOSXInputDevice*>(inContext);
dev->drainQueue();
} // Handle_ValueAvailableCallback
#endif
//
// FGMacOSXEventInput implementation
//
FGMacOSXEventInput::FGMacOSXEventInput() :
FGEventInput(),
d(new FGMacOSXEventInputPrivate)
{
d->p = this; // store back pointer to outer object on pimpl
SG_LOG(SG_INPUT, SG_DEBUG, "FGMacOSXEventInput created");
}
FGMacOSXEventInput::~FGMacOSXEventInput()
{
}
void FGMacOSXEventInput::init()
{
// everything is deffered until postinit since we need Nasal
}
void FGMacOSXEventInput::postinit()
{
d->hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
// set the HID device matching dictionary
IOHIDManagerSetDeviceMatching( d->hidManager, NULL /* all devices */);
IOHIDManagerRegisterDeviceMatchingCallback(d->hidManager, deviceMatchingCallback, d.get());
IOHIDManagerRegisterDeviceRemovalCallback(d->hidManager, deviceRemovalCallback, d.get());
IOHIDManagerOpen(d->hidManager, kIOHIDOptionsTypeNone);
IOHIDManagerScheduleWithRunLoop(d->hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
void FGMacOSXEventInput::shutdown()
{
FGEventInput::shutdown();
if (d->hidManager) {
IOHIDManagerClose(d->hidManager, kIOHIDOptionsTypeNone);
IOHIDManagerUnscheduleFromRunLoop(d->hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRelease(d->hidManager);
}
}
//
// read all elements in each input device
//
void FGMacOSXEventInput::update(double dt)
{
d->currentDt = dt;
d->currentModifiers = fgGetKeyModifiers();
FGEventInput::update(dt);
}
void FGMacOSXEventInputPrivate::matchedDevice(IOHIDDeviceRef device)
{
std::string productName = getDeviceStringProperty(device, CFSTR(kIOHIDProductKey));
std::string manufacturer = getDeviceStringProperty(device, CFSTR(kIOHIDManufacturerKey));
std::string serial = getDeviceStringProperty(device, CFSTR(kIOHIDSerialNumberKey));
// filter out keyboard and mouse devices : this is especially important for
// Catalina TCC hardening to avoid secuirty alerts
int usagePage, usage;
getDeviceIntProperty(device, CFSTR(kIOHIDPrimaryUsagePageKey), usagePage);
getDeviceIntProperty(device, CFSTR(kIOHIDPrimaryUsageKey), usage);
if (usagePage == kHIDPage_GenericDesktop) {
if ((usage == kHIDUsage_GD_Keyboard) || (usage == kHIDUsage_GD_Mouse)) {
SG_LOG(SG_INPUT, SG_INFO, "MacOSX-EventInput: skipping device:" << productName << "( from " << manufacturer << ") becuase it is a keyboard or mouse");
return;
}
}
SG_LOG(SG_INPUT, SG_DEBUG, "MacOSX-EventInput: matched device:" << productName << "( from " << manufacturer << ")");
// allocate a Mac input device, and add to the base class to see if we have
// a config
FGMacOSXInputDevice* macInputDevice = new FGMacOSXInputDevice(device, this);
macInputDevice->SetName(manufacturer + " " + productName);
macInputDevice->SetSerialNumber(serial);
p->AddDevice(macInputDevice);
}
void FGMacOSXEventInputPrivate::removedDevice(IOHIDDeviceRef device)
{
std::string productName = getDeviceStringProperty(device, CFSTR(kIOHIDProductKey));
std::string manufacturer = getDeviceStringProperty(device, CFSTR(kIOHIDManufacturerKey));
// see if we have an entry for the device
}
std::string FGMacOSXEventInputPrivate::getDeviceStringProperty(IOHIDDeviceRef device, CFStringRef hidProp)
{
CFStringRef prop = (CFStringRef) IOHIDDeviceGetProperty(device, hidProp);
if ((prop == nullptr)|| (CFGetTypeID(prop) != CFStringGetTypeID())) {
return std::string();
}
size_t len = CFStringGetLength(prop);
if (len == 0) {
return std::string();
}
char* buffer = static_cast<char*>(malloc(len + 1)); // + 1 for the null byte
Boolean ok = CFStringGetCString(prop, buffer, len + 1, kCFStringEncodingUTF8);
if (!ok) {
SG_LOG(SG_INPUT, SG_WARN, "string conversion failed");
}
std::string result(buffer, len);
free(buffer);
return result;
}
bool FGMacOSXEventInputPrivate::getDeviceIntProperty(IOHIDDeviceRef device, CFStringRef hidProp, int& value)
{
CFTypeRef prop = IOHIDDeviceGetProperty(device, hidProp);
if (CFGetTypeID(prop) != CFNumberGetTypeID()) {
return false;
}
int32_t v;
Boolean result = CFNumberGetValue((CFNumberRef) prop, kCFNumberSInt32Type, &v);
value = v;
return result;
}
void FGMacOSXEventInputPrivate::iterateDevices(CFSetRef matchingSet)
{
size_t numDevices = CFSetGetCount(matchingSet);
IOHIDDeviceRef* devs = static_cast<IOHIDDeviceRef*>(::malloc(numDevices * sizeof(IOHIDDeviceRef)));
CFSetGetValues(matchingSet, (const void **) devs);
for (size_t i=0; i < numDevices; ++i) {
matchedDevice(devs[i]);
}
free(devs);
}
FGMacOSXInputDevice::FGMacOSXInputDevice(IOHIDDeviceRef hidRef,
FGMacOSXEventInputPrivate* subsys)
{
_hid = hidRef;
CFRetain(_hid);
_subsystem = subsys;
}
FGMacOSXInputDevice::~FGMacOSXInputDevice()
{
if (_queue) {
CFRelease(_queue);
}
CFRelease(_hid);
}
bool FGMacOSXInputDevice::Open()
{
IOReturn result = IOHIDDeviceOpen(_hid, kIOHIDOptionsTypeNone);
if (result != kIOReturnSuccess) {
return false;
}
IOHIDDeviceScheduleWithRunLoop(_hid, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
// IOHIDQueueRegisterValueAvailableCallback(_queue, valueAvailableCallback, this);
CFIndex maxDepth = 128;
_queue = IOHIDQueueCreate(kCFAllocatorDefault, _hid, maxDepth, kIOHIDOptionsTypeNone);
IOHIDQueueScheduleWithRunLoop(_queue, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDQueueStart(_queue);
return true;
}
void FGMacOSXInputDevice::buildElementNameDictionary()
{
// copy all elements from the device
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(_hid, NULL,
kIOHIDOptionsTypeNone);
CFIndex count = CFArrayGetCount(elements);
typedef std::map<std::string, unsigned int> NameCountMap;
NameCountMap nameCounts;
std::set<IOHIDElementCookie> seenCookies;
for (int i = 0; i < count; ++i) {
IOHIDElementRef element = (IOHIDElementRef) CFArrayGetValueAtIndex(elements, i);
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
if (seenCookies.find(cookie) != seenCookies.end()) {
// skip duplicate match of this element;
continue;
}
seenCookies.insert(cookie);
// bool isWrapping = IOHIDElementIsWrapping(element);
IOHIDElementType ty = IOHIDElementGetType(element);
if (ty == kIOHIDElementTypeCollection)
{
continue;
}
uint32_t page = IOHIDElementGetUsagePage(element);
uint32_t usage = IOHIDElementGetUsage(element);
bool isRelative = IOHIDElementIsRelative(element);
// compute the name for the element
std::string name = nameForUsage(page, usage);
if (isRelative) {
name = "rel-" + name; // prefix relative elements
}
NameCountMap::iterator it = nameCounts.find(name);
unsigned int nameCount;
std::string finalName = name;
if (it == nameCounts.end()) {
nameCounts[name] = nameCount = 1;
} else {
// check if we have a collison but different element types, eg
// input & feature. In which case, prefix the feature one since
// we assume it's the input one which is more interesting.
IOHIDElementRef other = namedElements[name];
IOHIDElementType otherTy = IOHIDElementGetType(other);
if (otherTy != ty) {
// types mismatch
if (otherTy == kIOHIDElementTypeFeature) {
namedElements[name] = element;
element = other;
finalName = "feature-" + name;
} else if (ty == kIOHIDElementTypeFeature) {
finalName = "feature-" + name;
}
nameCount = 1;
} else {
// duplicate element, append ordinal suffix
std::stringstream os;
os << name << "-" << it->second;
finalName = os.str();
nameCount = ++(it->second);
}
}
CFRetain(element);
namedElements[finalName] = element;
if (nameCount == 2) {
// we have more than one entry for this name, so ensures
// the first item is availabe with the -0 suffix
std::stringstream os;
os << name << "-0";
namedElements[os.str()] = namedElements[name];
CFRetain(namedElements[os.str()]);
}
}
// HID debugging code
#if 0
NameElementDict::const_iterator it;
for (it = namedElements.begin(); it != namedElements.end(); ++it) {
int report = IOHIDElementGetReportID(it->second);
int reportCount = IOHIDElementGetReportCount(it->second);
int reportSize = IOHIDElementGetReportSize(it->second);
CFTypeRef sizeProp = IOHIDElementGetProperty(it->second, CFSTR(kIOHIDElementDuplicateIndexKey));
if (sizeProp) {
CFShow(sizeProp);
}
bool isArray = IOHIDElementIsArray(it->second);
if (isArray) {
SG_LOG(SG_INPUT, SG_INFO, "YYYYYYYYYYYYYYYYYY");
}
SG_LOG(SG_INPUT, SG_INFO, "\t" << it->first << " report ID " << report << " /count=" << reportCount
<< " ;sz=" << reportSize);
}
#endif
CFRelease(elements);
}
void FGMacOSXInputDevice::Close()
{
// leaking these otherwise we get a crash shutting down the HID-manager
// object. Don't understand why that should be the case
#if 0
for (auto it : namedElements) {
CFRelease(it.second);
}
#endif
namedElements.clear();
IOHIDQueueStop(_queue);
IOHIDQueueUnscheduleFromRunLoop(_queue, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDDeviceUnscheduleFromRunLoop(_hid, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDDeviceClose(_hid, kIOHIDOptionsTypeNone);
}
void FGMacOSXInputDevice::AddHandledEvent( FGInputEvent_ptr handledEvent )
{
SG_LOG(SG_INPUT, SG_DEBUG, "adding event:" << handledEvent->GetName());
if (namedElements.empty()) {
buildElementNameDictionary();
}
NameElementDict::iterator it = namedElements.find(handledEvent->GetName());
if (it == namedElements.end()) {
SG_LOG(SG_INPUT, SG_WARN, "device does not have any element with name:" << handledEvent->GetName());
return;
}
IOHIDQueueAddElement(_queue, it->second);
FGInputDevice::AddHandledEvent(handledEvent);
}
void FGMacOSXInputDevice::update(double dt)
{
SG_UNUSED(dt);
drainQueue();
FGInputDevice::update(dt);
}
void FGMacOSXInputDevice::drainQueue()
{
do {
IOHIDValueRef valueRef = IOHIDQueueCopyNextValueWithTimeout( _queue, 0. );
if ( !valueRef ) break;
handleValue(valueRef);
CFRelease( valueRef );
} while ( 1 ) ;
}
void FGMacOSXInputDevice::handleValue(IOHIDValueRef value)
{
IOHIDElementRef element = IOHIDValueGetElement(value);
CFIndex val;
// need report count to know if we have to handle this specially
int reportCount = IOHIDElementGetReportCount(element);
if (reportCount > 1) {
// for a repeated element, we need to read the final value of
// the data bytes (even though this will be the lowest numbered name
// for this element.
int bitSize = IOHIDElementGetReportSize(element);
const uint8_t* bytes= IOHIDValueGetBytePtr(value);
size_t byteLen = IOHIDValueGetLength(value);
uint8_t finalByte = bytes[byteLen - 1];
if (bitSize == 8) {
val = (int8_t) finalByte; // force sign extension
} else if (bitSize == 4) {
int8_t b = finalByte >> 4; // high nibble
if (b & 0x08) {
// manually sign extend
b |= 0xf0; // set all bits except the low nibble
}
val = b;
} else {
throw sg_io_exception("Unhandled bit size in MacoSXHID");
}
} else {
val = IOHIDValueGetIntegerValue(value);
}
// supress spurious 0-valued relative events
std::string name = nameForHIDElement(element);
if ((name.find("rel-") == 0) && (val == 0)) {
return;
}
FGMacOSXEventData eventData(name, val,
_subsystem->currentDt,
_subsystem->currentModifiers);
HandleEvent(eventData);
}
std::string FGMacOSXInputDevice::nameForHIDElement(IOHIDElementRef element) const
{
NameElementDict::const_iterator it;
for (it = namedElements.begin(); it != namedElements.end(); ++it) {
if (it->second == element) {
return it->first;
}
}
throw sg_exception("Unknown HID element");
}
const char *FGMacOSXInputDevice::TranslateEventName(FGEventData &eventData)
{
FGMacOSXEventData &macEvent = (FGMacOSXEventData &)eventData;
return macEvent.name.c_str();
}
//
// Outputs value to an writable element (like LEDElement)
//
void FGMacOSXInputDevice::Send(const char *eventName, double value)
{
NameElementDict::const_iterator it = namedElements.find(eventName);
if (it == namedElements.end()) {
SG_LOG(SG_INPUT, SG_WARN, "FGMacOSXInputDevice::Send: unknown element:" << eventName);
return;
}
CFIndex cfVal = value;
uint64_t timestamp = 0;
IOHIDValueRef valueRef = IOHIDValueCreateWithIntegerValue(kCFAllocatorDefault,
it->second, timestamp, cfVal);
IOHIDDeviceSetValue(_hid, it->second, valueRef);
CFRelease(valueRef);
}
void FGMacOSXInputDevice::SendFeatureReport(unsigned int reportId, const std::string& data)
{
string d(data);
if (reportId > 0) {
// prefix with report number
d.insert(d.begin(), static_cast<char>(reportId));
}
size_t len = d.size();
const uint8_t* bytes = (const uint8_t*) d.data();
std::stringstream ss;
for (int i=0; i<len; ++i) {
ss << static_cast<int>(bytes[i]);
ss << ":";
}
SG_LOG(SG_INPUT, SG_INFO, "report " << reportId << " sending " << ss.str());
IOReturn res = IOHIDDeviceSetReport(_hid,
kIOHIDReportTypeFeature,
reportId, /* Report ID*/
bytes, len);
if (res != kIOReturnSuccess) {
SG_LOG(SG_INPUT, SG_WARN, "failed to send feature report" << reportId);
}
}

View file

@ -1,67 +0,0 @@
// FGMacOSXEventInput.hxx -- handle event driven input devices for Mac OS X
//
// Written by Tatsuhiro Nishioka, started Aug. 2009.
//
// Copyright (C) 2009 Tasuhiro Nishioka, tat <dot> fgmacosx <at> gmail <dot> com
//
// 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.
//
// $Id$
#ifndef __FGMACOSXEVENTINPUT_HXX_
#define __FGMACOSXEVENTINPUT_HXX_
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "FGEventInput.hxx"
class FGMacOSXEventInputPrivate;
struct FGMacOSXEventData : public FGEventData {
FGMacOSXEventData(std::string name, double value, double dt, int modifiers) :
FGEventData(value, dt, modifiers), name(name) {}
std::string name;
};
//
// Mac OS X specific FGEventInput
//
class FGMacOSXEventInput : public FGEventInput
{
public:
FGMacOSXEventInput();
virtual ~FGMacOSXEventInput();
// Subsystem API.
void init() override;
void postinit() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input-event"; }
private:
friend class FGMacOSXEventInputPrivate;
std::unique_ptr<FGMacOSXEventInputPrivate> d;
};
#endif

View file

@ -561,7 +561,7 @@ void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindo
SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos); SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos);
if( updown != MOUSE_BUTTON_DOWN ) if( updown == MOUSE_BUTTON_UP )
{ {
// Execute the mouse up event in any case, may be we should // Execute the mouse up event in any case, may be we should
// stop processing here? // stop processing here?
@ -572,7 +572,7 @@ void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindo
{ {
SGPickCallbackPtr& cb = callbacks.front(); SGPickCallbackPtr& cb = callbacks.front();
const SGSceneryPick* pick = getPick(pickList, cb); const SGSceneryPick* pick = getPick(pickList, cb);
cb->buttonReleased(ea->getModKeyMask(), *ea, pick ? &pick->info : 0); cb->buttonReleased(ea->getModKeyMask(), *ea, pick ? &pick->info : nullptr);
callbacks.pop_front(); callbacks.pop_front();
} }

View file

@ -38,10 +38,9 @@
#ifdef WITH_EVENTINPUT #ifdef WITH_EVENTINPUT
#if defined ( SG_MAC ) #if defined ( SG_MAC )
#include "FGMacOSXEventInput.hxx" // we use HID
#define INPUTEVENT_CLASS FGMacOSXEventInput
#elif defined(SG_WINDOWS) #elif defined(SG_WINDOWS)
// we use HID
#else #else
#include "FGLinuxEventInput.hxx" #include "FGLinuxEventInput.hxx"
#define INPUTEVENT_CLASS FGLinuxEventInput #define INPUTEVENT_CLASS FGLinuxEventInput

View file

@ -101,6 +101,7 @@ endif()
# Set up the target links. # Set up the target links.
setup_fgfs_libraries(fgfs) setup_fgfs_libraries(fgfs)
export_debug_symbols(fgfs)
if (APPLE) if (APPLE)
install(TARGETS fgfs BUNDLE DESTINATION .) install(TARGETS fgfs BUNDLE DESTINATION .)
@ -108,6 +109,21 @@ else()
install(TARGETS fgfs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS fgfs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif() endif()
if (TARGET sentry::sentry)
# disabling this for now until Sentry-native fixes their CMake export :)
# target_link_libraries(fgfs sentry::sentry)
endif()
if (TARGET sentry_crashpad::handler)
if (APPLE)
# install inside the bundle
install(FILES $<TARGET_FILE:sentry_crashpad::handler> DESTINATION fgfs.app/Contents/MacOS OPTIONAL)
else()
# install in the bin-dir, next to the application binary
install(FILES $<TARGET_FILE:sentry_crashpad::handler> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
endif()
endif()
if(ENABLE_METAR) if(ENABLE_METAR)
add_executable(metar metar_main.cxx) add_executable(metar metar_main.cxx)
target_link_libraries(metar target_link_libraries(metar

View file

@ -389,7 +389,27 @@ private:
} else { } else {
SG_LOG(SG_AIRCRAFT, SG_DEV_ALERT, "Aircraft does not specify a minimum FG version: please add one at /sim/minimum-fg-version"); SG_LOG(SG_AIRCRAFT, SG_DEV_ALERT, "Aircraft does not specify a minimum FG version: please add one at /sim/minimum-fg-version");
} }
auto compatNodes = globals->get_props()->getNode("/sim")->getChildren("compatible-fg-version");
if (!compatNodes.empty()) {
bool showCompatWarning = true;
// if we have at least one compatibility node, then it needs to match
for (const auto& cn : compatNodes) {
const auto v = cn->getStringValue();
if (simgear::strutils::compareVersionToWildcard(FLIGHTGEAR_VERSION, v)) {
showCompatWarning = false;
break;
}
}
if (showCompatWarning) {
flightgear::modalMessageBox("Aircraft not compatible with this version",
"The selected aircraft has not been checked for compatability with this version of FlightGear (" FLIGHTGEAR_VERSION "). "
"Some aircraft features might not work, or might be displayed incorrectly.");
}
}
return true; return true;
} }
@ -628,7 +648,7 @@ int fgInitConfig ( int argc, char **argv, bool reinit )
#endif #endif
// Read global defaults from $FG_ROOT/defaults // Read global defaults from $FG_ROOT/defaults
SG_LOG(SG_GENERAL, SG_INFO, "Reading global defaults"); SG_LOG(SG_GENERAL, SG_DEBUG, "Reading global defaults");
SGPath defaultsXML = globals->get_fg_root() / "defaults.xml"; SGPath defaultsXML = globals->get_fg_root() / "defaults.xml";
if (!defaultsXML.exists()) { if (!defaultsXML.exists()) {
flightgear::fatalMessageBoxThenExit( flightgear::fatalMessageBoxThenExit(

View file

@ -216,11 +216,12 @@ FGIO::parse_port_config( const string_list& tokens )
"(one argument expected: --hla-local=<federationname>" ); "(one argument expected: --hla-local=<federationname>" );
return NULL; return NULL;
} }
tokens.insert(tokens.begin(), ""); std::vector<std::string> HLA_tokens (tokens);
tokens.insert(tokens.begin(), "60"); HLA_tokens.insert(HLA_tokens.begin(), "");
tokens.insert(tokens.begin(), "bi"); HLA_tokens.insert(HLA_tokens.begin(), "60");
tokens.push_back("fg-local.xml"); HLA_tokens.insert(HLA_tokens.begin(), "bi");
return new FGHLA(tokens); HLA_tokens.push_back("fg-local.xml");
return new FGHLA(HLA_tokens);
} }
#endif #endif
else { else {

View file

@ -326,28 +326,6 @@ getGMTString ()
return buf; return buf;
} }
/**
* Return the current heading in degrees.
*/
static double
getHeadingMag ()
{
double magheading = fgGetDouble("/orientation/heading-deg") -
fgGetDouble("/environment/magnetic-variation-deg");
return SGMiscd::normalizePeriodic(0, 360, magheading );
}
/**
* Return the current track in degrees.
*/
static double
getTrackMag ()
{
double magtrack = fgGetDouble("/orientation/track-deg") -
fgGetDouble("/environment/magnetic-variation-deg");
return SGMiscd::normalizePeriodic(0, 360, magtrack );
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Tie the properties. // Tie the properties.
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -439,9 +417,11 @@ FGProperties::bind ()
_tiedProperties.Tie<const char*>("/position/latitude-string", getLatitudeString); _tiedProperties.Tie<const char*>("/position/latitude-string", getLatitudeString);
_tiedProperties.Tie<const char*>("/position/longitude-string", getLongitudeString); _tiedProperties.Tie<const char*>("/position/longitude-string", getLongitudeString);
// Orientation _headingMagnetic = fgGetNode("/orientation/heading-magnetic-deg", true);
_tiedProperties.Tie<double>("/orientation/heading-magnetic-deg", getHeadingMag); _trackMagnetic = fgGetNode("/orientation/track-magnetic-deg", true);
_tiedProperties.Tie<double>("/orientation/track-magnetic-deg", getTrackMag); _magVar = fgGetNode("/environment/magnetic-variation-deg", true);
_trueHeading = fgGetNode("/orientation/heading-deg", true);
_trueTrack = fgGetNode("/orientation/track-deg", true);
} }
void void
@ -481,6 +461,10 @@ FGProperties::update (double dt)
_rmin->setIntValue(r->tm_min); _rmin->setIntValue(r->tm_min);
_rsec->setIntValue(r->tm_sec); _rsec->setIntValue(r->tm_sec);
_rwday->setIntValue(r->tm_wday); _rwday->setIntValue(r->tm_wday);
const double magvar = _magVar->getDoubleValue();
_headingMagnetic->setDoubleValue(_trueHeading->getDoubleValue() - magvar);
_trackMagnetic->setDoubleValue(_trueTrack->getDoubleValue() - magvar);
} }

View file

@ -43,6 +43,10 @@ private:
SGPropertyNode_ptr _offset; SGPropertyNode_ptr _offset;
SGPropertyNode_ptr _uyear, _umonth, _uday, _uhour, _umin, _usec, _uwday, _udsec; SGPropertyNode_ptr _uyear, _umonth, _uday, _uhour, _umin, _usec, _uwday, _udsec;
SGPropertyNode_ptr _ryear, _rmonth, _rday, _rhour, _rmin, _rsec, _rwday, _rdsec; SGPropertyNode_ptr _ryear, _rmonth, _rday, _rhour, _rmin, _rsec, _rwday, _rdsec;
SGPropertyNode_ptr _headingMagnetic, _trackMagnetic;
SGPropertyNode_ptr _magVar;
SGPropertyNode_ptr _trueHeading, _trueTrack;
}; };
@ -736,7 +740,7 @@ fgTie (const char * name, V (*getter)(), void (*setter)(V) = 0,
{ {
if (!globals->get_props()->tie(name, SGRawValueFunctions<V>(getter, setter), if (!globals->get_props()->tie(name, SGRawValueFunctions<V>(getter, setter),
useDefault)) useDefault))
SG_LOG(SG_GENERAL, SG_WARN, SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to functions"); "Failed to tie property " << name << " to functions");
} }
@ -769,7 +773,7 @@ fgTie (const char * name, int index, V (*getter)(int),
getter, getter,
setter), setter),
useDefault)) useDefault))
SG_LOG(SG_GENERAL, SG_WARN, SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to indexed functions"); "Failed to tie property " << name << " to indexed functions");
} }
@ -801,7 +805,7 @@ fgTie (const char * name, T * obj, V (T::*getter)() const,
if (!globals->get_props()->tie(name, if (!globals->get_props()->tie(name,
SGRawValueMethods<T,V>(*obj, getter, setter), SGRawValueMethods<T,V>(*obj, getter, setter),
useDefault)) useDefault))
SG_LOG(SG_GENERAL, SG_WARN, SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to object methods"); "Failed to tie property " << name << " to object methods");
} }
@ -837,7 +841,7 @@ fgTie (const char * name, T * obj, int index,
getter, getter,
setter), setter),
useDefault)) useDefault))
SG_LOG(SG_GENERAL, SG_WARN, SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to indexed object methods"); "Failed to tie property " << name << " to indexed object methods");
} }

View file

@ -21,9 +21,7 @@
// $Id$ // $Id$
#ifdef HAVE_CONFIG_H #include <config.h>
# include <config.h>
#endif
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
@ -165,7 +163,7 @@ void fgSetDefaults ()
fgSetDouble("/sim/presets/offset-distance-nm", 0.0); fgSetDouble("/sim/presets/offset-distance-nm", 0.0);
fgSetBool("/sim/presets/runway-requested", false); fgSetBool("/sim/presets/runway-requested", false);
fgSetBool("/sim/presets/onground", true); fgSetBool("/sim/presets/onground", true);
fgSetBool("/sim/presets/trim", false); fgSetBool("/sim/presets/trim", false);
@ -1421,22 +1419,22 @@ fgOptScenario( const char *arg )
// make absolute // make absolute
path = simgear::Dir::current().path() / arg; path = simgear::Dir::current().path() / arg;
} }
// create description node // create description node
auto n = FGAIManager::registerScenarioFile(globals->get_props(), path); auto n = FGAIManager::registerScenarioFile(globals->get_props(), path);
if (!n) { if (!n) {
SG_LOG(SG_GENERAL, SG_WARN, "failed to read scenario file at:" << path); SG_LOG(SG_GENERAL, SG_WARN, "failed to read scenario file at:" << path);
return FG_OPTIONS_ERROR; return FG_OPTIONS_ERROR;
} }
// also set the /sim/ai/scenario entry so we load it on startup // also set the /sim/ai/scenario entry so we load it on startup
name = path.file_base(); name = path.file_base();
} }
// add the 'load it' node // add the 'load it' node
SGPropertyNode_ptr ai_node = fgGetNode( "/sim/ai", true ); SGPropertyNode_ptr ai_node = fgGetNode( "/sim/ai", true );
ai_node->addChild("scenario")->setStringValue(name); ai_node->addChild("scenario")->setStringValue(name);
return FG_OPTIONS_OK; return FG_OPTIONS_OK;
} }
@ -1680,6 +1678,7 @@ struct OptionDesc {
{"ndb", true, OPTION_FUNC, "", false, "", fgOptNDB }, {"ndb", true, OPTION_FUNC, "", false, "", fgOptNDB },
{"ndb-frequency", true, OPTION_DOUBLE, "/sim/presets/ndb-freq", false, "", fgOptVOR }, {"ndb-frequency", true, OPTION_DOUBLE, "/sim/presets/ndb-freq", false, "", fgOptVOR },
{"carrier", true, OPTION_FUNC, "", false, "", fgOptCarrier }, {"carrier", true, OPTION_FUNC, "", false, "", fgOptCarrier },
{"carrier-abeam", true, OPTION_BOOL, "/sim/presets/carrier-abeam", true, "", 0 },
{"parkpos", true, OPTION_FUNC, "", false, "", fgOptParkpos }, {"parkpos", true, OPTION_FUNC, "", false, "", fgOptParkpos },
{"fix", true, OPTION_FUNC, "", false, "", fgOptFIX }, {"fix", true, OPTION_FUNC, "", false, "", fgOptFIX },
{"offset-distance", true, OPTION_DOUBLE, "/sim/presets/offset-distance-nm", false, "", 0 }, {"offset-distance", true, OPTION_DOUBLE, "/sim/presets/offset-distance-nm", false, "", 0 },
@ -1719,6 +1718,7 @@ struct OptionDesc {
{"notrim", false, OPTION_BOOL, "/sim/presets/trim", false, "", 0 }, {"notrim", false, OPTION_BOOL, "/sim/presets/trim", false, "", 0 },
{"on-ground", false, OPTION_BOOL, "/sim/presets/onground", true, "", 0 }, {"on-ground", false, OPTION_BOOL, "/sim/presets/onground", true, "", 0 },
{"in-air", false, OPTION_BOOL, "/sim/presets/onground", false, "", 0 }, {"in-air", false, OPTION_BOOL, "/sim/presets/onground", false, "", 0 },
{"disable-hold-short", false, OPTION_BOOL, "/sim/presets/mp-hold-short-override", true, "", 0 },
{"fog-disable", false, OPTION_STRING, "/sim/rendering/fog", false, "disabled", 0 }, {"fog-disable", false, OPTION_STRING, "/sim/rendering/fog", false, "disabled", 0 },
{"fog-fastest", false, OPTION_STRING, "/sim/rendering/fog", false, "fastest", 0 }, {"fog-fastest", false, OPTION_STRING, "/sim/rendering/fog", false, "fastest", 0 },
{"fog-nicest", false, OPTION_STRING, "/sim/rendering/fog", false, "nicest", 0 }, {"fog-nicest", false, OPTION_STRING, "/sim/rendering/fog", false, "nicest", 0 },

View file

@ -52,8 +52,8 @@ using std::string;
namespace flightgear namespace flightgear
{ {
enum InitPosResult { enum InitPosResult {
ExactPosition, ExactPosition,
VicinityPosition, VicinityPosition,
@ -180,7 +180,9 @@ std::tuple<SGGeod, double> runwayStartPos(FGRunwayRef runway)
double startOffset = fgGetDouble("/sim/airport/runways/start-offset-m", 5.0); double startOffset = fgGetDouble("/sim/airport/runways/start-offset-m", 5.0);
SGGeod pos = runway->pointOnCenterline(startOffset); SGGeod pos = runway->pointOnCenterline(startOffset);
if (FGIO::isMultiplayerRequested() && (fabs(offsetNm) <0.1)) { const bool overrideHoldShort = fgGetBool("/sim/presets/mp-hold-short-override", false);
if (!overrideHoldShort && FGIO::isMultiplayerRequested() && (fabs(offsetNm) <0.1)) {
SG_LOG( SG_GENERAL, SG_WARN, "Requested to start on " << runway->airport()->ident() << "/" << SG_LOG( SG_GENERAL, SG_WARN, "Requested to start on " << runway->airport()->ident() << "/" <<
runway->ident() << ", MP is enabled so computing hold short position to avoid runway incursion"); runway->ident() << ", MP is enabled so computing hold short position to avoid runway incursion");
@ -417,7 +419,7 @@ static InitPosResult setInitialPosFromCarrier( const string& carrier )
SG_LOG( SG_GENERAL, SG_DEBUG, "Initial carrier pos = " << initialPos.second ); SG_LOG( SG_GENERAL, SG_DEBUG, "Initial carrier pos = " << initialPos.second );
return VicinityPosition; return VicinityPosition;
} }
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = " << carrier ); SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = " << carrier );
return Failure; return Failure;
} }
@ -450,33 +452,33 @@ static InitPosResult checkCarrierSceneryLoaded(const SGSharedPtr<FGAICarrier> ca
// Set current_options lon/lat given an aircraft carrier id // Set current_options lon/lat given an aircraft carrier id
static InitPosResult setFinalPosFromCarrier( const string& carrier, const string& posid ) static InitPosResult setFinalPosFromCarrier( const string& carrier, const string& posid )
{ {
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier); SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
if (!carrierRef) { if (!carrierRef) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = " SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = "
<< carrier ); << carrier );
return Failure; return Failure;
} }
auto res = checkCarrierSceneryLoaded(carrierRef); auto res = checkCarrierSceneryLoaded(carrierRef);
if (res != VicinityPosition) { if (res != VicinityPosition) {
return res; // either failrue or keep waiting for scenery load return res; // either failrue or keep waiting for scenery load
} }
SGGeod geodPos; SGGeod geodPos;
double heading; double heading;
SGVec3d uvw; SGVec3d uvw;
if (carrierRef->getParkPosition(posid, geodPos, heading, uvw)) { if (carrierRef->getParkPosition(posid, geodPos, heading, uvw)) {
//////// ////////
double lon = geodPos.getLongitudeDeg(); double lon = geodPos.getLongitudeDeg();
double lat = geodPos.getLatitudeDeg(); double lat = geodPos.getLatitudeDeg();
double alt = geodPos.getElevationFt() + 2.0; double alt = geodPos.getElevationFt() + 2.0;
SG_LOG( SG_GENERAL, SG_INFO, "Attempting to set starting position for " SG_LOG( SG_GENERAL, SG_INFO, "Attempting to set starting position for "
<< carrier << " at lat = " << lat << ", lon = " << lon << carrier << " at lat = " << lat << ", lon = " << lon
<< ", alt = " << alt << ", heading = " << heading); << ", alt = " << alt << ", heading = " << heading);
fgSetDouble("/sim/presets/longitude-deg", lon); fgSetDouble("/sim/presets/longitude-deg", lon);
fgSetDouble("/sim/presets/latitude-deg", lat); fgSetDouble("/sim/presets/latitude-deg", lat);
fgSetDouble("/sim/presets/altitude-ft", alt); fgSetDouble("/sim/presets/altitude-ft", alt);
@ -485,7 +487,7 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
fgSetDouble("/position/latitude-deg", lat); fgSetDouble("/position/latitude-deg", lat);
fgSetDouble("/position/altitude-ft", alt); fgSetDouble("/position/altitude-ft", alt);
fgSetDouble("/orientation/heading-deg", heading); fgSetDouble("/orientation/heading-deg", heading);
fgSetString("/sim/presets/speed-set", "UVW"); fgSetString("/sim/presets/speed-set", "UVW");
fgSetDouble("/velocities/uBody-fps", uvw(0)); fgSetDouble("/velocities/uBody-fps", uvw(0));
fgSetDouble("/velocities/vBody-fps", uvw(1)); fgSetDouble("/velocities/vBody-fps", uvw(1));
@ -493,9 +495,9 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
fgSetDouble("/sim/presets/uBody-fps", uvw(0)); fgSetDouble("/sim/presets/uBody-fps", uvw(0));
fgSetDouble("/sim/presets/vBody-fps", uvw(1)); fgSetDouble("/sim/presets/vBody-fps", uvw(1));
fgSetDouble("/sim/presets/wBody-fps", uvw(2)); fgSetDouble("/sim/presets/wBody-fps", uvw(2));
fgSetBool("/sim/presets/onground", true); fgSetBool("/sim/presets/onground", true);
///////// /////////
return ExactPosition; return ExactPosition;
} }
@ -503,7 +505,7 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
return Failure; return Failure;
} }
static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier) static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier, bool abeam)
{ {
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier); SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
if (!carrierRef) { if (!carrierRef) {
@ -529,16 +531,29 @@ static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier)
double gs = SGMiscd::deg2rad(carrierRef->getFLOLFSGlidepathAngleDeg()); double gs = SGMiscd::deg2rad(carrierRef->getFLOLFSGlidepathAngleDeg());
const double od = fgGetDouble("/sim/presets/offset-distance-nm"); const double od = fgGetDouble("/sim/presets/offset-distance-nm");
// start position, but with altitude not set // start position
SGGeod startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS + 180, od * SG_NM_TO_METER); SGGeod startPos;
if (abeam) {
// If we're starting from the abeam position, we are opposite the FLOLS, downwind on a left hand circuit
startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS - 90, od * SG_NM_TO_METER);
} else {
startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS + 180, od * SG_NM_TO_METER);
}
const double offsetFt = od * SG_NM_TO_METER * SG_METER_TO_FEET; double alt = fgGetDouble("/sim/presets/altitude-ft");
startPos.setElevationFt(fabs(offsetFt*tan(gs)) + flolsElevationFt);
if (alt < 0.0f) {
// No altitude set, so base on glideslope
const double offsetFt = od * SG_NM_TO_METER * SG_METER_TO_FEET;
startPos.setElevationFt(fabs(offsetFt*tan(gs)) + flolsElevationFt);
} else {
startPos.setElevationFt(alt);
}
fgSetDouble("/sim/presets/longitude-deg", startPos.getLongitudeDeg()); fgSetDouble("/sim/presets/longitude-deg", startPos.getLongitudeDeg());
fgSetDouble("/sim/presets/latitude-deg", startPos.getLatitudeDeg()); fgSetDouble("/sim/presets/latitude-deg", startPos.getLatitudeDeg());
fgSetDouble("/sim/presets/altitude-ft", startPos.getElevationFt()); fgSetDouble("/sim/presets/altitude-ft", startPos.getElevationFt());
fgSetDouble("/sim/presets/heading-deg", headingToFLOLS); fgSetDouble("/sim/presets/heading-deg", abeam ? (headingToFLOLS - 180) : headingToFLOLS);
fgSetDouble("/position/longitude-deg", startPos.getLongitudeDeg()); fgSetDouble("/position/longitude-deg", startPos.getLongitudeDeg());
fgSetDouble("/position/latitude-deg", startPos.getLatitudeDeg()); fgSetDouble("/position/latitude-deg", startPos.getLatitudeDeg());
fgSetDouble("/position/altitude-ft", startPos.getElevationFt()); fgSetDouble("/position/altitude-ft", startPos.getElevationFt());
@ -576,21 +591,21 @@ bool initPosition()
globals->get_event_mgr()->addTask("finalizePosition", &finalizePosition, 0.1); globals->get_event_mgr()->addTask("finalizePosition", &finalizePosition, 0.1);
global_callbackRegistered = true; global_callbackRegistered = true;
} }
double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg")); double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg"));
double od = fgGetDouble("/sim/presets/offset-distance-nm"); double od = fgGetDouble("/sim/presets/offset-distance-nm");
double alt = fgGetDouble("/sim/presets/altitude-ft"); double alt = fgGetDouble("/sim/presets/altitude-ft");
bool set_pos = false; bool set_pos = false;
// If glideslope is specified, then calculate offset-distance or // If glideslope is specified, then calculate offset-distance or
// altitude relative to glide slope if either of those was not // altitude relative to glide slope if either of those was not
// specified. // specified.
if ( fabs( gs ) > 0.01 ) { if ( fabs( gs ) > 0.01 ) {
fgSetDistOrAltFromGlideSlope(); fgSetDistOrAltFromGlideSlope();
} }
// If we have an explicit, in-range lon/lat, don't change it, just use it. // If we have an explicit, in-range lon/lat, don't change it, just use it.
// If not, check for an airport-id and use that. // If not, check for an airport-id and use that.
// If not, default to the middle of the KSFO field. // If not, default to the middle of the KSFO field.
@ -604,7 +619,7 @@ bool initPosition()
{ {
set_pos = true; set_pos = true;
} }
string apt = fgGetString("/sim/presets/airport-id"); string apt = fgGetString("/sim/presets/airport-id");
const bool apt_req = fgGetBool("/sim/presets/airport-requested"); const bool apt_req = fgGetBool("/sim/presets/airport-requested");
string rwy_no = fgGetString("/sim/presets/runway"); string rwy_no = fgGetString("/sim/presets/runway");
@ -616,31 +631,31 @@ bool initPosition()
string carrier = fgGetString("/sim/presets/carrier"); string carrier = fgGetString("/sim/presets/carrier");
string parkpos = fgGetString("/sim/presets/parkpos"); string parkpos = fgGetString("/sim/presets/parkpos");
string fix = fgGetString("/sim/presets/fix"); string fix = fgGetString("/sim/presets/fix");
// the launcher sets this to precisely identify a navaid // the launcher sets this to precisely identify a navaid
PositionedID navaidId = fgGetInt("/sim/presets/navaid-id"); PositionedID navaidId = fgGetInt("/sim/presets/navaid-id");
SGPropertyNode *hdg_preset = fgGetNode("/sim/presets/heading-deg", true); SGPropertyNode *hdg_preset = fgGetNode("/sim/presets/heading-deg", true);
double hdg = hdg_preset->getDoubleValue(); double hdg = hdg_preset->getDoubleValue();
// save some start parameters, so that we can later say what the // save some start parameters, so that we can later say what the
// user really requested. TODO generalize that and move it to options.cxx // user really requested. TODO generalize that and move it to options.cxx
static bool start_options_saved = false; static bool start_options_saved = false;
if (!start_options_saved) { if (!start_options_saved) {
start_options_saved = true; start_options_saved = true;
SGPropertyNode *opt = fgGetNode("/sim/startup/options", true); SGPropertyNode *opt = fgGetNode("/sim/startup/options", true);
opt->setDoubleValue("latitude-deg", lat_deg); opt->setDoubleValue("latitude-deg", lat_deg);
opt->setDoubleValue("longitude-deg", lon_deg); opt->setDoubleValue("longitude-deg", lon_deg);
opt->setDoubleValue("heading-deg", hdg); opt->setDoubleValue("heading-deg", hdg);
opt->setStringValue("airport", apt.c_str()); opt->setStringValue("airport", apt.c_str());
opt->setStringValue("runway", rwy_no.c_str()); opt->setStringValue("runway", rwy_no.c_str());
} }
if (hdg > 9990.0) { if (hdg > 9990.0) {
hdg = fgGetDouble("/environment/config/boundary/entry/wind-from-heading-deg", 270); hdg = fgGetDouble("/environment/config/boundary/entry/wind-from-heading-deg", 270);
} }
if ( !set_pos && !carrier.empty() ) { if ( !set_pos && !carrier.empty() ) {
// an aircraft carrier is requested // an aircraft carrier is requested
const auto result = setInitialPosFromCarrier( carrier ); const auto result = setInitialPosFromCarrier( carrier );
@ -649,7 +664,7 @@ bool initPosition()
set_pos = true; set_pos = true;
} }
} }
if (apt_req && !rwy_req) { if (apt_req && !rwy_req) {
// ensure that if the users asks for a specific airport, but not a runway, // ensure that if the users asks for a specific airport, but not a runway,
// presumably because they want automatic selection, we do not look // presumably because they want automatic selection, we do not look
@ -657,7 +672,7 @@ bool initPosition()
// likely missing. // likely missing.
rwy_no.clear(); rwy_no.clear();
} }
if ( !set_pos && !apt.empty() && !parkpos.empty() ) { if ( !set_pos && !apt.empty() && !parkpos.empty() ) {
// An airport + parking position is requested // An airport + parking position is requested
// since this depends on parking, which is part of dynamics, and hence // since this depends on parking, which is part of dynamics, and hence
@ -670,7 +685,7 @@ bool initPosition()
set_pos = true; set_pos = true;
} }
} }
if ( !set_pos && !apt.empty() && !rwy_no.empty() ) { if ( !set_pos && !apt.empty() && !rwy_no.empty() ) {
// An airport + runway is requested // An airport + runway is requested
if ( fgSetPosFromAirportIDandRwy( apt, rwy_no, rwy_req ) ) { if ( fgSetPosFromAirportIDandRwy( apt, rwy_no, rwy_req ) ) {
@ -681,7 +696,7 @@ bool initPosition()
set_pos = true; set_pos = true;
} }
} }
if ( !set_pos && !apt.empty() ) { if ( !set_pos && !apt.empty() ) {
// An airport is requested (find runway closest to hdg) // An airport is requested (find runway closest to hdg)
if ( setPosFromAirportIDandHdg( apt, hdg ) ) { if ( setPosFromAirportIDandHdg( apt, hdg ) ) {
@ -692,31 +707,31 @@ bool initPosition()
set_pos = true; set_pos = true;
} }
} }
if (hdg_preset->getDoubleValue() > 9990.0) if (hdg_preset->getDoubleValue() > 9990.0)
hdg_preset->setDoubleValue(hdg); hdg_preset->setDoubleValue(hdg);
if ( !set_pos && !vor.empty() ) { if ( !set_pos && !vor.empty() ) {
// a VOR is requested // a VOR is requested
if ( fgSetPosFromNAV( vor, vor_freq, FGPositioned::VOR, navaidId ) ) { if ( fgSetPosFromNAV( vor, vor_freq, FGPositioned::VOR, navaidId ) ) {
set_pos = true; set_pos = true;
} }
} }
if ( !set_pos && !ndb.empty() ) { if ( !set_pos && !ndb.empty() ) {
// an NDB is requested // an NDB is requested
if ( fgSetPosFromNAV( ndb, ndb_freq, FGPositioned::NDB, navaidId ) ) { if ( fgSetPosFromNAV( ndb, ndb_freq, FGPositioned::NDB, navaidId ) ) {
set_pos = true; set_pos = true;
} }
} }
if ( !set_pos && !fix.empty() ) { if ( !set_pos && !fix.empty() ) {
// a Fix is requested // a Fix is requested
if ( fgSetPosFromFix( fix, navaidId ) ) { if ( fgSetPosFromFix( fix, navaidId ) ) {
set_pos = true; set_pos = true;
} }
} }
if ( !set_pos ) { if ( !set_pos ) {
const std::string defaultAirportId = fgGetString("/sim/presets/airport-id"); const std::string defaultAirportId = fgGetString("/sim/presets/airport-id");
const FGAirport* airport = fgFindAirportID(defaultAirportId); const FGAirport* airport = fgFindAirportID(defaultAirportId);
@ -733,27 +748,27 @@ bool initPosition()
<< "') seems to be unknown."); << "') seems to be unknown.");
} }
} }
fgSetDouble( "/position/longitude-deg", fgSetDouble( "/position/longitude-deg",
fgGetDouble("/sim/presets/longitude-deg") ); fgGetDouble("/sim/presets/longitude-deg") );
fgSetDouble( "/position/latitude-deg", fgSetDouble( "/position/latitude-deg",
fgGetDouble("/sim/presets/latitude-deg") ); fgGetDouble("/sim/presets/latitude-deg") );
fgSetDouble( "/orientation/heading-deg", hdg_preset->getDoubleValue()); fgSetDouble( "/orientation/heading-deg", hdg_preset->getDoubleValue());
// determine if this should be an on-ground or in-air start // determine if this should be an on-ground or in-air start
if ((fabs(gs) > 0.01 || fabs(od) > 0.1 || alt > 0.1) && carrier.empty()) { if ((fabs(gs) > 0.01 || fabs(od) > 0.1 || alt > 0.1) && carrier.empty()) {
fgSetBool("/sim/presets/onground", false); fgSetBool("/sim/presets/onground", false);
} else { } else {
fgSetBool("/sim/presets/onground", true); fgSetBool("/sim/presets/onground", true);
} }
fgSetBool("/sim/position-finalized", false); fgSetBool("/sim/position-finalized", false);
// Initialize the longitude, latitude and altitude to the initial position // Initialize the longitude, latitude and altitude to the initial position
fgSetDouble("/position/altitude-ft", fgGetDouble("/sim/presets/altitude-ft")); fgSetDouble("/position/altitude-ft", fgGetDouble("/sim/presets/altitude-ft"));
fgSetDouble("/position/longitude-deg", fgGetDouble("/sim/presets/longitude-deg")); fgSetDouble("/position/longitude-deg", fgGetDouble("/sim/presets/longitude-deg"));
fgSetDouble("/position/latitude-deg", fgGetDouble("/sim/presets/latitude-deg")); fgSetDouble("/position/latitude-deg", fgGetDouble("/sim/presets/latitude-deg"));
return true; return true;
} }
@ -820,13 +835,14 @@ void finalizePosition()
std::string runway = fgGetString("/sim/presets/runway"); std::string runway = fgGetString("/sim/presets/runway");
std::string apt = fgGetString("/sim/presets/airport-id"); std::string apt = fgGetString("/sim/presets/airport-id");
const bool rwy_req = fgGetBool("/sim/presets/runway-requested"); const bool rwy_req = fgGetBool("/sim/presets/runway-requested");
const bool abeam = fgGetBool("/sim/presets/carrier-abeam");
if (!carrier.empty()) if (!carrier.empty())
{ {
const bool atFLOLS = rwy_req && (runway == "FLOLS"); const bool atFLOLS = rwy_req && (runway == "FLOLS" || parkpos == "FLOLS");
InitPosResult carrierResult; InitPosResult carrierResult;
if (atFLOLS) { if (atFLOLS) {
carrierResult = setFinalPosFromCarrierFLOLS(carrier); carrierResult = setFinalPosFromCarrierFLOLS(carrier, abeam);
} else { } else {
carrierResult = setFinalPosFromCarrier(carrier, parkpos); carrierResult = setFinalPosFromCarrier(carrier, parkpos);
} }
@ -843,7 +859,7 @@ void finalizePosition()
done = true; done = true;
} }
} }
} else if (!apt.empty() && !parkpos.empty()) { } else if (!apt.empty() && !parkpos.empty()) {
// parking position depends on ATC / dynamics code to assign spaces, // parking position depends on ATC / dynamics code to assign spaces,
// so we wait until this point to initialise // so we wait until this point to initialise

View file

@ -2449,9 +2449,9 @@ FGMultiplayMgr::addMultiplayer(const std::string& callsign,
} }
} }
/* Copy values from our local <set>/sim/view[]/config/* into global // Copy values from our local <set>/sim/view[]/config/* into global
/ai/models/multiplayer/set/sim/view[]/config/ so that we have view offsets // /ai/models/multiplayer/set/sim/view[]/config/ so that we have view offsets
available for this multiplayer aircraft. */ // available for this multiplayer aircraft.
SGPropertyNode* global_set = mp->_getProps()->addChild("set"); SGPropertyNode* global_set = mp->_getProps()->addChild("set");
SGPropertyNode* global_sim = global_set->addChild("sim"); SGPropertyNode* global_sim = global_set->addChild("sim");
double sim_chase_distance_m = -25; double sim_chase_distance_m = -25;

View file

@ -62,7 +62,12 @@ public:
_manager(manager), _manager(manager),
_useVBOsProp(fgGetNode("/sim/rendering/use-vbos", true)), _useVBOsProp(fgGetNode("/sim/rendering/use-vbos", true)),
_enableCacheProp(fgGetNode("/sim/tile-cache/enable", true)), _enableCacheProp(fgGetNode("/sim/tile-cache/enable", true)),
_pagedLODMaximumProp(fgGetNode("/sim/rendering/max-paged-lod", true)) _pagedLODMaximumProp(fgGetNode("/sim/rendering/max-paged-lod", true)),
_lodDetailed(fgGetNode("/sim/rendering/static-lod/detailed", true)),
_lodRoughDelta(fgGetNode("/sim/rendering/static-lod/rough-delta", true)),
_lodBareDelta(fgGetNode("/sim/rendering/static-lod/bare-delta", true)),
_lodRough(fgGetNode("/sim/rendering/static-lod/rough", true)),
_lodBare(fgGetNode("/sim/rendering/static-lod/bare", true))
{ {
_useVBOsProp->addChangeListener(this, true); _useVBOsProp->addChangeListener(this, true);
@ -78,6 +83,9 @@ public:
_pagedLODMaximumProp->setIntValue(current); _pagedLODMaximumProp->setIntValue(current);
} }
_pagedLODMaximumProp->addChangeListener(this, true); _pagedLODMaximumProp->addChangeListener(this, true);
_lodDetailed->addChangeListener(this, true);
_lodBareDelta->addChangeListener(this, true);
_lodRoughDelta->addChangeListener(this, true);
} }
~TileManagerListener() ~TileManagerListener()
@ -85,6 +93,9 @@ public:
_useVBOsProp->removeChangeListener(this); _useVBOsProp->removeChangeListener(this);
_enableCacheProp->removeChangeListener(this); _enableCacheProp->removeChangeListener(this);
_pagedLODMaximumProp->removeChangeListener(this); _pagedLODMaximumProp->removeChangeListener(this);
_lodDetailed->removeChangeListener(this);
_lodBareDelta->removeChangeListener(this);
_lodRoughDelta->removeChangeListener(this);
} }
virtual void valueChanged(SGPropertyNode* prop) virtual void valueChanged(SGPropertyNode* prop)
@ -92,13 +103,20 @@ public:
if (prop == _useVBOsProp) { if (prop == _useVBOsProp) {
bool useVBOs = prop->getBoolValue(); bool useVBOs = prop->getBoolValue();
_manager->_options->setPluginStringData("SimGear::USE_VBOS", _manager->_options->setPluginStringData("SimGear::USE_VBOS",
useVBOs ? "ON" : "OFF"); useVBOs ? "ON" : "OFF");
} else if (prop == _enableCacheProp) { } else if (prop == _enableCacheProp) {
_manager->_enableCache = prop->getBoolValue(); _manager->_enableCache = prop->getBoolValue();
} else if (prop == _pagedLODMaximumProp) { } else if (prop == _pagedLODMaximumProp) {
int v = prop->getIntValue(); int v = prop->getIntValue();
osg::ref_ptr<osgViewer::Viewer> viewer(globals->get_renderer()->getViewer()); osg::ref_ptr<osgViewer::Viewer> viewer(globals->get_renderer()->getViewer());
viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(v); viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(v);
} else if (prop == _lodDetailed || prop == _lodBareDelta || prop == _lodRoughDelta) {
// compatibility with earlier versions; set the static lod ranges appropriately as otherwise (bad) self managed
// LOD on scenery with range animations doesn't work.
// see also /sim/rendering/enable-range-lod-animations - which is false by default in > 2019.2 which also fixes
// the scenery but in a more efficient way.
_lodBare->setDoubleValue(_lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue());
_lodRough->setDoubleValue(_lodBare->getDoubleValue() + _lodBareDelta->getDoubleValue());
} }
} }
@ -106,7 +124,13 @@ private:
FGTileMgr* _manager; FGTileMgr* _manager;
SGPropertyNode_ptr _useVBOsProp, SGPropertyNode_ptr _useVBOsProp,
_enableCacheProp, _enableCacheProp,
_pagedLODMaximumProp; _pagedLODMaximumProp,
_lodDetailed,
_lodRoughDelta,
_lodBareDelta,
_lodRough,
_lodBare
;
}; };
FGTileMgr::FGTileMgr(): FGTileMgr::FGTileMgr():

View file

@ -21,6 +21,7 @@
#include "config.h" #include "config.h"
#include <cstring> #include <cstring>
#include <algorithm>
#include "NasalPositioned.hxx" #include "NasalPositioned.hxx"
@ -2446,7 +2447,8 @@ private:
class NasalFPDelegateFactory : public FlightPlan::DelegateFactory class NasalFPDelegateFactory : public FlightPlan::DelegateFactory
{ {
public: public:
NasalFPDelegateFactory(naRef code) NasalFPDelegateFactory(naRef code, const std::string& id) :
_id(id)
{ {
_nasal = globals->get_subsystem<FGNasalSys>(); _nasal = globals->get_subsystem<FGNasalSys>();
_func = code; _func = code;
@ -2474,7 +2476,11 @@ public:
naFreeContext(ctx); naFreeContext(ctx);
return result; return result;
} }
const std::string& id() const
{ return _id; }
private: private:
const std::string _id;
FGNasalSys* _nasal; FGNasalSys* _nasal;
naRef _func; naRef _func;
int _gcSaveKey; int _gcSaveKey;
@ -2499,10 +2505,45 @@ static naRef f_registerFPDelegate(naContext c, naRef me, int argc, naRef* args)
if ((argc < 1) || !naIsFunc(args[0])) { if ((argc < 1) || !naIsFunc(args[0])) {
naRuntimeError(c, "non-function argument to registerFlightPlanDelegate"); naRuntimeError(c, "non-function argument to registerFlightPlanDelegate");
} }
NasalFPDelegateFactory* factory = new NasalFPDelegateFactory(args[0]);
FlightPlan::registerDelegateFactory(factory); const std::string delegateId = (argc > 1) ? naStr_data(args[1]) : std::string{};
if (!delegateId.empty()) {
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
[delegateId](NasalFPDelegateFactory* delegate) {
return delegate->id() == delegateId;
});
if (it != static_nasalDelegateFactories.end()) {
naRuntimeError(c, "duplicate delegate ID at registerFlightPlanDelegate: %s", delegateId.c_str());
}
}
NasalFPDelegateFactory* factory = new NasalFPDelegateFactory(args[0], delegateId);
FlightPlan::registerDelegateFactory(factory);
static_nasalDelegateFactories.push_back(factory); static_nasalDelegateFactories.push_back(factory);
return naNil(); return naNil();
}
static naRef f_unregisterFPDelegate(naContext c, naRef me, int argc, naRef* args)
{
if ((argc < 1) || !naIsString(args[0])) {
naRuntimeError(c, "non-string argument to unregisterFlightPlanDelegate");
}
const std::string delegateId = naStr_data(args[0]);
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
[delegateId](NasalFPDelegateFactory* delegate) {
return delegate->id() == delegateId;
});
if (it == static_nasalDelegateFactories.end()) {
SG_LOG(SG_NASAL, SG_DEV_WARN, "f_unregisterFPDelegate: no de;egate with ID:" << delegateId);
return naNil();
}
FlightPlan::unregisterDelegateFactory(*it);
static_nasalDelegateFactories.erase(it);
return naNil();
} }
static WayptRef wayptFromArg(naRef arg) static WayptRef wayptFromArg(naRef arg)
@ -3295,6 +3336,7 @@ static struct { const char* name; naCFunction func; } funcs[] = {
{ "flightplan", f_flightplan }, { "flightplan", f_flightplan },
{ "createFlightplan", f_createFlightplan }, { "createFlightplan", f_createFlightplan },
{ "registerFlightPlanDelegate", f_registerFPDelegate }, { "registerFlightPlanDelegate", f_registerFPDelegate },
{ "unregisterFlightPlanDelegate", f_unregisterFPDelegate},
{ "createWP", f_createWP }, { "createWP", f_createWP },
{ "createWPFrom", f_createWPFrom }, { "createWPFrom", f_createWPFrom },
{ "createViaTo", f_createViaTo }, { "createViaTo", f_createViaTo },

View file

@ -955,6 +955,17 @@ naRef FGNasalSys::cmdArgGhost()
return propNodeGhost(_cmdArg); return propNodeGhost(_cmdArg);
} }
void FGNasalSys::initLogLevelConstants()
{
hashset(_globals, "LOG_BULK", naNum(SG_BULK));
hashset(_globals, "LOG_WARN", naNum(SG_WARN));
hashset(_globals, "LOG_DEBUG", naNum(SG_DEBUG));
hashset(_globals, "LOG_INFO", naNum(SG_INFO));
hashset(_globals, "LOG_ALERT", naNum(SG_ALERT));
hashset(_globals, "DEV_WARN", naNum(SG_DEV_WARN));
hashset(_globals, "DEV_ALERT", naNum(SG_DEV_ALERT));
}
void FGNasalSys::setCmdArg(SGPropertyNode* aNode) void FGNasalSys::setCmdArg(SGPropertyNode* aNode)
{ {
_cmdArg = aNode; _cmdArg = aNode;
@ -983,6 +994,8 @@ void FGNasalSys::init()
hashset(_globals, "thread", naInit_thread(_context)); hashset(_globals, "thread", naInit_thread(_context));
hashset(_globals, "utf8", naInit_utf8(_context)); hashset(_globals, "utf8", naInit_utf8(_context));
initLogLevelConstants();
// Add our custom extension functions: // Add our custom extension functions:
for(i=0; funcs[i].name; i++) for(i=0; funcs[i].name; i++)
hashset(_globals, funcs[i].name, hashset(_globals, funcs[i].name,

View file

@ -161,6 +161,18 @@ public:
simgear::BufferedLogCallback* log() const simgear::BufferedLogCallback* log() const
{ return _log.get(); } { return _log.get(); }
private:
void initLogLevelConstants();
void loadPropertyScripts();
void loadPropertyScripts(SGPropertyNode* n);
void loadScriptDirectory(simgear::Dir nasalDir);
void addModule(std::string moduleName, simgear::PathList scripts);
static void logError(naContext);
naRef parse(naContext ctx, const char* filename, const char* buf, int len,
std::string& errors);
naRef genPropsModule();
private: private:
//friend class FGNasalScript; //friend class FGNasalScript;
friend class FGNasalListener; friend class FGNasalListener;
@ -180,15 +192,6 @@ private:
static int _listenerId; static int _listenerId;
void loadPropertyScripts();
void loadPropertyScripts(SGPropertyNode* n);
void loadScriptDirectory(simgear::Dir nasalDir);
void addModule(std::string moduleName, simgear::PathList scripts);
static void logError(naContext);
naRef parse(naContext ctx, const char* filename, const char* buf, int len,
std::string& errors);
naRef genPropsModule();
bool _inited; bool _inited;
naContext _context; naContext _context;
naRef _globals, naRef _globals,

View file

@ -505,7 +505,7 @@ void FGTrafficManager::shutdown()
bool FGTrafficManager::doDataSync() bool FGTrafficManager::doDataSync()
{ {
simgear::SGTerraSync* terraSync = static_cast<simgear::SGTerraSync*>(globals->get_subsystem("terrasync")); auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
bool doDataSync = fgGetBool("/sim/terrasync/ai-data-enabled"); bool doDataSync = fgGetBool("/sim/terrasync/ai-data-enabled");
if (doDataSync && terraSync) { if (doDataSync && terraSync) {
if (!trafficSyncRequested) { if (!trafficSyncRequested) {

View file

@ -171,7 +171,7 @@ View* View::createFromProperties(SGPropertyNode_ptr config, int view_index)
double fov_deg = config->getDoubleValue("default-field-of-view-deg"); double fov_deg = config->getDoubleValue("default-field-of-view-deg");
double near_m = config->getDoubleValue("ground-level-nearplane-m"); double near_m = config->getDoubleValue("ground-level-nearplane-m");
View* v = 0; View* v = nullptr;
// supporting two types "lookat" = 1 and "lookfrom" = 0 // supporting two types "lookat" = 1 and "lookfrom" = 0
const char *type = config->getParent()->getStringValue("type"); const char *type = config->getParent()->getStringValue("type");
if (!strcmp(type, "lookat")) { if (!strcmp(type, "lookat")) {

View file

@ -21,7 +21,7 @@ target_link_libraries(fgai
SimGearCore SimGearScene SimGearCore SimGearScene
${OPENSCENEGRAPH_LIBRARIES} ${OPENSCENEGRAPH_LIBRARIES}
${OPENGL_LIBRARIES} ${OPENGL_LIBRARIES}
${RTI_LIBRARIES} ${RTI_LDFLAGS}
) )
install(TARGETS fgai RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS fgai RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

0
utils/fgcom/positions.hxx Normal file → Executable file
View file

View file

@ -16,7 +16,6 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
// //
#ifdef _WIN32 #ifdef _WIN32
# include <direct.h> // for getcwd() # include <direct.h> // for getcwd()
#else // !_WIN32 #else // !_WIN32

View file

@ -1,13 +1,26 @@
find_package(PNG) find_package(PNG QUIET)
find_package(OpenGL) find_package(OpenGL QUIET)
find_package(GLEW) find_package(Freetype QUIET)
find_package(Freetype)
if ((NOT PNG_FOUND) OR (NOT OPENGL_FOUND) OR (NOT GLEW_FOUND) OR (NOT FREETYPE_FOUND)) find_package(GLUT QUIET)
find_package(GLEW QUIET)
if(NOT ${GLUT_FOUND})
message(WARNING "GLUT NOT found, can't build FGPanel")
set(WITH_FGPANEL 0)
return()
endif()
if(NOT ${GLEW_FOUND})
message(WARNING "GLEW NOT found, can't build FGPanel")
set(WITH_FGPANEL 0)
return()
endif()
if((NOT PNG_FOUND) OR (NOT OPENGL_FOUND) OR (NOT FREETYPE_FOUND))
message(WARNING "FGPanel enabled, but some dependencies are missing") message(WARNING "FGPanel enabled, but some dependencies are missing")
message(STATUS "libPNG: ${PNG_FOUND}") message(STATUS "libPNG: ${PNG_FOUND}")
message(STATUS "OpenGL: ${OPENGL_FOUND}") message(STATUS "OpenGL: ${OPENGL_FOUND}")
message(STATUS "GLEW: ${GLEW_FOUND}")
message(STATUS "Freetype: ${FREETYPE_FOUND}") message(STATUS "Freetype: ${FREETYPE_FOUND}")
return() return()
endif() endif()
@ -18,21 +31,7 @@ find_path(BCMHOST_INCLUDE_DIR
NO_DEFAULT_PATH NO_DEFAULT_PATH
) )
include_directories( set(TARGET_SOURCES
${FREETYPE_INCLUDE_DIRS}
${PNG_INCLUDE_DIR}
)
if(${BCMHOST_INCLUDE_DIR} STREQUAL "BCMHOST_INCLUDE_DIR-NOTFOUND")
else()
# CMAKE > 3.1 : target_sources(fgpanel
set(TARGET_SOURCES
GLES_utils.cxx
GLES_utils.hxx
)
endif()
add_executable(fgpanel
main.cxx main.cxx
ApplicationProperties.hxx ApplicationProperties.hxx
ApplicationProperties.cxx ApplicationProperties.cxx
@ -74,45 +73,71 @@ add_executable(fgpanel
panel_io.hxx panel_io.hxx
GL_utils.cxx GL_utils.cxx
GL_utils.hxx GL_utils.hxx
${TARGET_SOURCES}
) )
add_executable(fgpanel ${TARGET_SOURCES})
target_link_libraries(fgpanel target_link_libraries(fgpanel
SimGearCore SimGearCore
${PNG_LIBRARIES} ${PNG_LIBRARIES}
${FREETYPE_LIBRARIES} ${FREETYPE_LIBRARIES}
${OPENGL_LIBRARIES}
${GLUT_LIBRARIES}
${GLEW_LIBRARIES}
) )
if(${BCMHOST_INCLUDE_DIR} STREQUAL "BCMHOST_INCLUDE_DIR-NOTFOUND") target_include_directories(fgpanel PUBLIC
find_package(GLUT REQUIRED) ${FREETYPE_INCLUDE_DIRS}
if(GLUT_FOUND) ${PNG_INCLUDE_DIR}
message(STATUS "found GLUT inc ${GLUT_INCLUDE_DIR}, lib ${GLUT_LIBRARIES} ") ${GLEW_INCLUDE_DIRS}
if (MSVC) )
add_definitions( -DFREEGLUT_LIB_PRAGMAS=0 )
endif ()
target_link_libraries(fgpanel if(MSVC)
${OPENGL_LIBRARIES} target_compile_definitions(fgpanel PUBLIC
${GLUT_LIBRARIES} -DFREEGLUT_LIB_PRAGMAS=0
GLEW::GLEW
) )
else(GLUT_FOUND) endif(MSVC)
message(STATUS "glut NOT found, can't build fgpanel")
endif(GLUT_FOUND) if(BCMHOST_INCLUDE_DIR)
else()
message(STATUS "found Raspberry Pi") message(STATUS "found Raspberry Pi")
target_link_libraries(fgpanel add_executable(fgpanel-egl ${TARGET_SOURCES}
-lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib GLES_utils.cxx
GLES_utils.hxx
) )
target_include_directories(fgpanel-egl PUBLIC
include_directories( ${FREETYPE_INCLUDE_DIRS}
${PNG_INCLUDE_DIR}
${BCMHOST_INCLUDE_DIR} ${BCMHOST_INCLUDE_DIR}
${BCMHOST_INCLUDE_DIR}/interface/vcos/pthreads ${BCMHOST_INCLUDE_DIR}/interface/vcos/pthreads
${BCMHOST_INCLUDE_DIR}/interface/vmcs_host/linux ${BCMHOST_INCLUDE_DIR}/interface/vmcs_host/linux
) )
add_definitions(-D_GLES2 -D_RPI) target_link_libraries(fgpanel-egl
endif() SimGearCore
${PNG_LIBRARIES}
${FREETYPE_LIBRARIES}
-lbrcmGLESv2 -lbrcmEGL -lm -lbcm_host -L/opt/vc/lib
)
install(TARGETS fgpanel RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) target_compile_definitions(fgpanel-egl PUBLIC
-D_GLES2 -D_RPI
)
target_compile_definitions(fgpanel PUBLIC
-D_RPI
)
message(STATUS "FGPanel (Raspberry Pi) : ENABLED")
install(TARGETS fgpanel-egl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
else(BCMHOST_INCLUDE_DIR)
message(STATUS "FGPanel (Raspberry Pi) : DISABLED")
endif(BCMHOST_INCLUDE_DIR)
if(WITH_FGPANEL)
message(STATUS "FGPanel : ENABLED")
install(TARGETS fgpanel RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
else(WITH_FGPANEL)
message(WARNING "FGPanel : DISABLED")
endif(WITH_FGPANEL)

View file

@ -89,12 +89,13 @@ void
FGPanel::init () { FGPanel::init () {
// Textured Layer Shaders // Textured Layer Shaders
const char V_Textured_Layer_Shader_Str[] = const char V_Textured_Layer_Shader_Str[] =
#ifdef _GLES2 #ifdef _RPI
"#version 100 \n"
"attribute vec4 a_position; \n" "attribute vec4 a_position; \n"
"attribute vec2 a_tex_coord; \n" "attribute vec2 a_tex_coord; \n"
"varying vec2 v_tex_coord; \n" "varying vec2 v_tex_coord; \n"
#else #else
"#version 330 \n" "#version 130 \n"
"in vec4 a_position; \n" "in vec4 a_position; \n"
"in vec2 a_tex_coord; \n" "in vec2 a_tex_coord; \n"
"out vec2 v_tex_coord; \n" "out vec2 v_tex_coord; \n"
@ -106,13 +107,13 @@ FGPanel::init () {
"} \n"; "} \n";
const char F_Textured_Layer_Shader_Str[] = const char F_Textured_Layer_Shader_Str[] =
#ifdef _GLES2 #ifdef _RPI
"#version 100 \n"
"precision mediump float; \n" "precision mediump float; \n"
"varying vec2 v_tex_coord; \n" "varying vec2 v_tex_coord; \n"
#else #else
"#version 330 \n" "#version 130 \n"
"in vec2 v_tex_coord; \n" "in vec2 v_tex_coord; \n"
"out vec4 gl_FragColor; \n"
#endif #endif
"uniform sampler2D u_texture; \n" "uniform sampler2D u_texture; \n"
"void main () { \n" "void main () { \n"

View file

@ -51,8 +51,7 @@ using namespace std;
* redraw themselves when necessary, and will pass mouse clicks on to * redraw themselves when necessary, and will pass mouse clicks on to
* the appropriate instruments for processing. * the appropriate instruments for processing.
*/ */
class FGPanel : public SGSubsystem class FGPanel : public SGSubsystem {
{
public: public:
FGPanel (const SGPropertyNode_ptr root); FGPanel (const SGPropertyNode_ptr root);
virtual ~FGPanel (); virtual ~FGPanel ();

View file

@ -36,7 +36,6 @@
#include <GLUT/glut.h> #include <GLUT/glut.h>
#elif defined (_GLES2) #elif defined (_GLES2)
#include <GLES2/gl2.h> #include <GLES2/gl2.h>
#include "GLES_utils.hxx"
#else #else
#include <GL/glew.h> // Must be included before <GL/gl.h> #include <GL/glew.h> // Must be included before <GL/gl.h>
#include <GL/gl.h> #include <GL/gl.h>

View file

@ -28,8 +28,7 @@ class PropertySetter;
typedef vector<PropertySetter*> PropertySetterVector; typedef vector<PropertySetter*> PropertySetterVector;
class FGPanelProtocol : public SGSubsystem class FGPanelProtocol : public SGSubsystem {
{
public: public:
FGPanelProtocol (SGPropertyNode_ptr a_Root); FGPanelProtocol (SGPropertyNode_ptr a_Root);
virtual ~FGPanelProtocol (); virtual ~FGPanelProtocol ();

View file

@ -35,26 +35,32 @@ GLint FGTextLayer::Text_Layer_Color_Loc (0);
bool bool
FGTextLayer::Init () { FGTextLayer::Init () {
const char V_Text_Layer_Shader_Str[] = const char V_Text_Layer_Shader_Str[] =
#ifdef _GLES2 #ifdef _RPI
"#version 100 \n"
"attribute vec4 a_position; \n" "attribute vec4 a_position; \n"
"attribute vec2 a_tex_coord; \n" "attribute vec2 a_tex_coord; \n"
"varying vec2 v_tex_coord; \n"
#else #else
"#version 330 \n" "#version 130 \n"
"in vec4 a_position; \n" "in vec4 a_position; \n"
"in vec2 a_tex_coord; \n" "in vec2 a_tex_coord; \n"
"out vec2 v_tex_coord; \n"
#endif #endif
"uniform mat4 u_mvp_matrix; \n" "uniform mat4 u_mvp_matrix; \n"
"varying vec2 v_tex_coord; \n"
"void main () { \n" "void main () { \n"
" gl_Position = u_mvp_matrix * a_position; \n" " gl_Position = u_mvp_matrix * a_position; \n"
" v_tex_coord = a_tex_coord; \n" " v_tex_coord = a_tex_coord; \n"
"} \n"; "} \n";
const char F_Text_Layer_Shader_Str[] = const char F_Text_Layer_Shader_Str[] =
#ifdef _GLES2 #ifdef _RPI
"#version 100 \n"
"precision mediump float; \n" "precision mediump float; \n"
#endif
"varying vec2 v_tex_coord; \n" "varying vec2 v_tex_coord; \n"
#else
"#version 130 \n"
"in vec2 v_tex_coord; \n"
#endif
"uniform sampler2D u_texture; \n" "uniform sampler2D u_texture; \n"
"uniform vec4 u_color; \n" "uniform vec4 u_color; \n"
"void main () { \n" "void main () { \n"

View file

@ -15,10 +15,10 @@ INSTALLATION
The source code of FGPanel can easily be adapted to other embedded devices supporting OpenGL ES 2.0. The source code of FGPanel can easily be adapted to other embedded devices supporting OpenGL ES 2.0.
3. Build FGPanel only (don't build all components on the Raspberry Pi as this will take ages!!!): 3. Build FGPanel only (don't build all components on the Raspberry Pi as this will take ages!!!):
make -- fgpanel make -- fgpanel-egl fgpanel
USAGE USAGE (fgpanel-egl)
===== ===================
1. Increase the amount of GPU memory by editing the /boot/config.txt file. 1. Increase the amount of GPU memory by editing the /boot/config.txt file.
Add the following line (this is for Raspberry Pi model 1B with 512 Mb of memory): Add the following line (this is for Raspberry Pi model 1B with 512 Mb of memory):
@ -30,10 +30,21 @@ USAGE
3. Stop X server. FGPanel runs in the console (Linux framebuffer). 3. Stop X server. FGPanel runs in the console (Linux framebuffer).
4. Start FGPanel as usual but as ROOT or use the 'sudo' command (see README): 4. Start FGPanel as usual but as ROOT or use the 'sudo' command (see README):
sudo -- utils/fgpanel/fgpanel --fg-root=/path/to/fg/data --panel=Aircraft/MyAircraft/Panels/MyPanel.xml sudo -- utils/fgpanel/fgpanel-egl --fg-root=/path/to/fg/data --panel=Aircraft/MyAircraft/Panels/MyPanel.xml
ROOT privileges are required to access the GPU of the Raspberry Pi. ROOT privileges are required to access the GPU of the Raspberry Pi.
USAGE (fgpanel)
===================
1. Activate the Full KMS driver on the Raspberry Pi using the raspi-config program:
Advanced Options -> GL Driver -> Full KMS
2. Reboot. The Full KMS driver should be activated.
3. When X server is running, start FGPanel as usual (see README):
utils/fgpanel/fgpanel --fg-root=/path/to/fg/data --panel=Aircraft/MyAircraft/Panels/MyPanel.xml
FEATURES FEATURES
======== ========

View file

@ -31,8 +31,7 @@
#include "FGPanel.hxx" #include "FGPanel.hxx"
class FGReadablePanel : public FGPanel class FGReadablePanel : public FGPanel {
{
public: public:
// Subsystem identification. // Subsystem identification.
static const char* staticSubsystemClassId() { return "readable-panel"; } static const char* staticSubsystemClassId() { return "readable-panel"; }

View file

@ -35,7 +35,7 @@ if(RTI_FOUND)
HLAWindowDrawable.cxx HLAWindowDrawable.cxx
HLAWindowDrawableClass.cxx HLAWindowDrawableClass.cxx
) )
set(FGVIEWER_RTI_LIBRARIES ${RTI_LIBRARIES}) set(FGVIEWER_RTI_LIBRARIES ${RTI_LDFLAGS})
else() else()
set(FGVIEWER_RTI_LIBRARIES "") set(FGVIEWER_RTI_LIBRARIES "")
set(FGVIEWER_RTI_SOURCES "") set(FGVIEWER_RTI_SOURCES "")
@ -47,9 +47,9 @@ if(X11_FOUND)
endif() endif()
target_link_libraries(fgviewer target_link_libraries(fgviewer
SimGearScene SimGearCore SimGearScene SimGearCore
${OPENGL_LIBRARIES} ${OPENGL_LIBRARIES}
${FGVIEWER_RTI_LIBRARIES} ${FGVIEWER_RTI_LIBRARIES}
${GDAL_LIBRARY} ${GDAL_LIBRARY}
) )
install(TARGETS fgviewer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS fgviewer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})