diff --git a/src/Autopilot/analogcomponent.cxx b/src/Autopilot/analogcomponent.cxx index f161d88cf..549c69bc0 100644 --- a/src/Autopilot/analogcomponent.cxx +++ b/src/Autopilot/analogcomponent.cxx @@ -120,3 +120,11 @@ bool AnalogComponent::configure( SGPropertyNode& cfg_node, return Component::configure(cfg_node, cfg_name, prop_root); } + +void AnalogComponent::collectDependentProperties(std::set& props) const +{ + _valueInput.collectDependentProperties(props); + _referenceInput.collectDependentProperties(props); + _minInput.collectDependentProperties(props); + _maxInput.collectDependentProperties(props); +} diff --git a/src/Autopilot/analogcomponent.hxx b/src/Autopilot/analogcomponent.hxx index f7e6b76fa..f5682baeb 100644 --- a/src/Autopilot/analogcomponent.hxx +++ b/src/Autopilot/analogcomponent.hxx @@ -139,6 +139,12 @@ protected: public: const PeriodicalValue * getPeriodicalValue() const { return _periodical; } + + /** + Add to all properties that are used by this component. Similar to + SGExpression::collectDependentProperties(). + */ + void collectDependentProperties(std::set& props) const; }; inline void AnalogComponent::disabled( double dt ) diff --git a/src/Autopilot/digitalfilter.cxx b/src/Autopilot/digitalfilter.cxx index 56f1e8a9e..f4292c232 100644 --- a/src/Autopilot/digitalfilter.cxx +++ b/src/Autopilot/digitalfilter.cxx @@ -26,6 +26,8 @@ // #include "digitalfilter.hxx" +#include +#include
#include #include @@ -52,7 +54,7 @@ class DigitalFilterImplementation: SGPropertyNode& prop_root ) = 0; void setDigitalFilter( DigitalFilter * digitalFilter ) { _digitalFilter = digitalFilter; } - + virtual void collectDependentProperties(std::set& props) const = 0; protected: DigitalFilter * _digitalFilter; }; @@ -68,6 +70,10 @@ protected: public: GainFilterImplementation() : _gainInput(1.0) {} double compute( double dt, double input ); + virtual void collectDependentProperties(std::set& props) const + { + _gainInput.collectDependentProperties(props); + } }; class ReciprocalFilterImplementation : public GainFilterImplementation { @@ -85,6 +91,11 @@ public: DerivativeFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + GainFilterImplementation::collectDependentProperties(props); + _TfInput.collectDependentProperties(props); + } }; class ExponentialFilterImplementation : public GainFilterImplementation { @@ -99,6 +110,11 @@ public: ExponentialFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + GainFilterImplementation::collectDependentProperties(props); + _TfInput.collectDependentProperties(props); + } }; class MovingAverageFilterImplementation : public DigitalFilterImplementation { @@ -113,6 +129,10 @@ public: MovingAverageFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + _samplesInput.collectDependentProperties(props); + } }; class NoiseSpikeFilterImplementation : public DigitalFilterImplementation { @@ -126,6 +146,10 @@ public: NoiseSpikeFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + _rateOfChangeInput.collectDependentProperties(props); + } }; class RateLimitFilterImplementation : public DigitalFilterImplementation { @@ -140,6 +164,11 @@ public: RateLimitFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + _rateOfChangeMax.collectDependentProperties(props); + _rateOfChangeMin.collectDependentProperties(props); + } }; class IntegratorFilterImplementation : public GainFilterImplementation { @@ -156,6 +185,13 @@ public: IntegratorFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + GainFilterImplementation::collectDependentProperties(props); + _TfInput.collectDependentProperties(props); + _minInput.collectDependentProperties(props); + _maxInput.collectDependentProperties(props); + } }; // integrates x" + ax' + bx + c = 0 @@ -174,6 +210,13 @@ public: DampedOscillationFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + GainFilterImplementation::collectDependentProperties(props); + _aInput.collectDependentProperties(props); + _bInput.collectDependentProperties(props); + _cInput.collectDependentProperties(props); + } }; class HighPassFilterImplementation : public GainFilterImplementation { @@ -188,6 +231,11 @@ public: HighPassFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + GainFilterImplementation::collectDependentProperties(props); + _TfInput.collectDependentProperties(props); + } }; class LeadLagFilterImplementation : public GainFilterImplementation { protected: @@ -202,6 +250,12 @@ public: LeadLagFilterImplementation(); double compute( double dt, double input ); virtual void initialize( double initvalue ); + virtual void collectDependentProperties(std::set& props) const + { + GainFilterImplementation::collectDependentProperties(props); + _TfaInput.collectDependentProperties(props); + _TfbInput.collectDependentProperties(props); + } }; class CoherentNoiseFilterImplementation : public DigitalFilterImplementation @@ -221,6 +275,10 @@ public: CoherentNoiseFilterImplementation(); double compute(double dt, double input) override; void initialize(double initvalue) override; + virtual void collectDependentProperties(std::set& props) const + { + _amplitude.collectDependentProperties(props); + } }; /* --------------------------------------------------------------------------------- */ @@ -846,20 +904,37 @@ bool DigitalFilter::configure( SGPropertyNode& prop_root, { SGPropertyNode_ptr child = cfg.getChild(i); std::string cname(child->getName()); - - if( !_implementation->configure(*child, cname, prop_root) - && !configure(*child, cname, prop_root) - && cname != "type" - && cname != "params" ) // 'params' is usually used to specify parameters - // in PropertList files. + bool ok = false; + if (!ok) ok = _implementation->configure(*child, cname, prop_root); + if (!ok) ok = configure(*child, cname, prop_root); + if (!ok) ok = (cname == "type"); + if (!ok) ok = (cname == "params"); // 'params' is usually used to specify parameters in PropertList files. + if (!ok) { SG_LOG ( SG_AUTOPILOT, - SG_WARN, + SG_ALERT, "DigitalFilter: unknown config node: " << cname ); + } } - + + /* Send information about associations between our input and output + properties to Highlight. */ + std::set inputs; + _implementation->collectDependentProperties(inputs); + collectDependentProperties(inputs); + + Highlight* highlight = globals->get_subsystem(); + for (auto in: inputs) { + for (auto& out: _output_list) { + highlight->add_property_property( + in->getPath(true /*simplify*/), + out->getPath(true /*simplify*/) + ); + } + } + return true; } diff --git a/src/Autopilot/inputvalue.cxx b/src/Autopilot/inputvalue.cxx index 501874436..1e597edf6 100644 --- a/src/Autopilot/inputvalue.cxx +++ b/src/Autopilot/inputvalue.cxx @@ -225,3 +225,19 @@ double InputValue::get_value() const return _abs ? fabs(value) : value; } +void InputValue::collectDependentProperties(std::set& props) const +{ + if (_property) props.insert(_property); + if (_offset) _offset->collectDependentProperties(props); + if (_scale) _scale->collectDependentProperties(props); + if (_min) _min->collectDependentProperties(props); + if (_max) _max->collectDependentProperties(props); + if (_expression) _expression->collectDependentProperties(props); +} + +void InputValueList::collectDependentProperties(std::set& props) const +{ + for (auto& iv: *this) { + iv->collectDependentProperties(props); + } +} diff --git a/src/Autopilot/inputvalue.hxx b/src/Autopilot/inputvalue.hxx index de5dd4900..aba7676d9 100644 --- a/src/Autopilot/inputvalue.hxx +++ b/src/Autopilot/inputvalue.hxx @@ -109,6 +109,7 @@ public: return _condition == NULL ? true : _condition->test(); } + void collectDependentProperties(std::set& props) const; }; /** @@ -134,6 +135,8 @@ class InputValueList : public std::vector { InputValue_ptr input = get_active(); return input == NULL ? _def : input->get_value(); } + + void collectDependentProperties(std::set& props) const; private: double _def; diff --git a/src/FDM/YASim/FGFDM.cpp b/src/FDM/YASim/FGFDM.cpp index 06e1d7c68..244c0a15e 100644 --- a/src/FDM/YASim/FGFDM.cpp +++ b/src/FDM/YASim/FGFDM.cpp @@ -62,6 +62,18 @@ FGFDM::~FGFDM() delete _turb; } +void FGFDM::property_associations( + void* ref, + void (*fn)(void* ref, const std::string& from, const std::string& to) + ) +{ + for (auto& a: _property_to_properties) { + for (auto& b: a.second) { + fn(ref, a.first, b); + } + } +} + void FGFDM::iterate(float dt) { getExternalInput(dt); @@ -209,12 +221,45 @@ void FGFDM::init() _airplane.getModel()->setTurbulence(_turb); } +void FGFDM::endElement(const char* name) +{ + _xml_depth -= 1; +} + // Not the worlds safest parser. But it's short & sweet. void FGFDM::startElement(const char* name, const XMLAttributes &a) { + _xml_depth += 1; //XMLAttributes* a = (XMLAttributes*)&atts; float v[3] {0,0,0}; + if (_xml_depth < _xml_last_control_depth) { + // We use _control_to_input_properties and + // _control_to_output_properties to link input/output properties via + // their 'control' attributes. For example a vstab might have: + // + // + // + // + // When parsing we associate FLAP0 with + // /controls/flight/flaps in _control_to_input_properties. Similarly + // when parsing we associate FLAPS0 with + // /surface-positions/flap-pos-norm in _control_to_output_properties. + // + // We can then use _control_to_input_properties and + // _control_to_output_properties to associate properties + // that are associated with the same control - in this case + // /controls/flight/flaps and /surface-positions/flap-pos-norm are both + // associated with FLAPS0. + // + // To make this work, we need to clear these maps when we have finished + // parsing each container of and , + // which we do here. + // + _control_to_input_properties.clear(); + _control_to_output_properties.clear(); + } + if(!strcmp(name, "airplane")) { parseAirplane(&a); } else if(!strcmp(name, "approach") || !strcmp(name, "cruise")) { parseApproachCruise(&a, name); @@ -255,8 +300,14 @@ void FGFDM::startElement(const char* name, const XMLAttributes &a) ((Thruster*)_currObj)->setDirection(v); } else if(!strcmp(name, "control-setting")) { parseControlSetting(&a); } - else if(!strcmp(name, "control-input")) { parseControlIn(&a); } - else if(!strcmp(name, "control-output")) { parseControlOut(&a); } + else if(!strcmp(name, "control-input")) { + _xml_last_control_depth = _xml_depth; + parseControlIn(&a); + } + else if(!strcmp(name, "control-output")) { + _xml_last_control_depth = _xml_depth; + parseControlOut(&a); + } else if(!strcmp(name, "control-speed")) { parseControlSpeed(&a); } else { SG_LOG(SG_FLIGHT,SG_ALERT,"Unexpected tag '" << name << "' found in YASim aircraft description"); @@ -1201,6 +1252,14 @@ void FGFDM::parseControlIn(const XMLAttributes* a) dst1 = attrf(a, "dst1"); } _airplane.addControlInput(a->getValue("axis"), control, _currObj, _wingSection, opt, src0, src1, dst0, dst1); + + // Detect new associations between properties. + std::string control_name = a->getValue("control"); + std::string in = a->getValue("axis"); + _control_to_input_properties[control_name].insert(in); + for (auto& out: _control_to_output_properties[control_name]) { + _property_to_properties[in].insert(out); + } } void FGFDM::parseControlOut(const XMLAttributes* a) @@ -1220,6 +1279,14 @@ void FGFDM::parseControlOut(const XMLAttributes* a) p->min = attrf(a, "min", cm->rangeMin(control)); p->max = attrf(a, "max", cm->rangeMax(control)); _controlOutputs.add(p); + + // Detect new associations between properties. + std::string control_name = a->getValue("control"); + std::string out = a->getValue("prop"); + _control_to_output_properties[control_name].insert(out); + for (auto& in: _control_to_input_properties[control_name]) { + _property_to_properties[in].insert(out); + } } void FGFDM::parseControlSpeed(const XMLAttributes* a) diff --git a/src/FDM/YASim/FGFDM.hpp b/src/FDM/YASim/FGFDM.hpp index 30663721a..c9b03f9dd 100644 --- a/src/FDM/YASim/FGFDM.hpp +++ b/src/FDM/YASim/FGFDM.hpp @@ -7,6 +7,11 @@ #include "Airplane.hpp" #include "Vector.hpp" +#include +#include +#include + + namespace yasim { class Wing; @@ -27,8 +32,14 @@ public: // XML parsing callback from XMLVisitor virtual void startElement(const char* name, const XMLAttributes &atts); + virtual void endElement(const char* name); float getVehicleRadius(void) const { return _vehicle_radius; } + + void property_associations( + void* ref, + void (*fn)(void* ref, const std::string& from, const std::string& to) + ); private: struct EngRec { @@ -118,6 +129,8 @@ private: Airplane::Configuration _airplaneCfg; int _nextEngine {0}; int _wingSection {0}; + int _xml_depth {0}; + int _xml_last_control_depth {0}; class FuelProps { @@ -159,6 +172,13 @@ private: SGPropertyNode_ptr _aryN; SGPropertyNode_ptr _arzN; SGPropertyNode_ptr _cg_xmacN; + + // Used on startup when gathering information about associated properties, + // e.g. /controls/flight/flaps and /surface-positions/flap-pos-norm. + std::map> _control_to_input_properties; + std::map> _control_to_output_properties; + std::map> _property_to_properties; + }; }; // namespace yasim diff --git a/src/FDM/YASim/YASim.cxx b/src/FDM/YASim/YASim.cxx index 1647ddca7..bf8f54169 100644 --- a/src/FDM/YASim/YASim.cxx +++ b/src/FDM/YASim/YASim.cxx @@ -54,6 +54,14 @@ YASim::~YASim() _gearProps.clear(); } +void YASim::property_associations( + void* ref, + void (*fn)(void* ref, const std::string& from, const std::string& to) + ) +{ + return _fdm->property_associations(ref, fn); +} + void YASim::report() { Airplane* a = _fdm->getAirplane(); diff --git a/src/FDM/YASim/YASim.hxx b/src/FDM/YASim/YASim.hxx index 37bfa6c4d..f51979db0 100644 --- a/src/FDM/YASim/YASim.hxx +++ b/src/FDM/YASim/YASim.hxx @@ -21,6 +21,11 @@ public: // Subsystem identification. static const char* staticSubsystemClassId() { return "yasim"; } + void property_associations( + void* ref, + void (*fn)(void* ref, const std::string& from, const std::string& to) + ) override; + private: void report(); void copyFromYASim(); diff --git a/src/FDM/flight.cxx b/src/FDM/flight.cxx index 1909c648c..3008dcf8f 100644 --- a/src/FDM/flight.cxx +++ b/src/FDM/flight.cxx @@ -233,6 +233,14 @@ FGInterface::common_init () SG_LOG( SG_FLIGHT, SG_INFO, "End common FDM init" ); } +void FGInterface::property_associations( + void* ref, + void (*fn)(void* ref, const std::string& from, const std::string& to) + ) +{ + /* Do nothing by default. */ +} + /** * Bind getters and setters to properties. diff --git a/src/FDM/flight.hxx b/src/FDM/flight.hxx index 83fb11f75..a807f28bb 100644 --- a/src/FDM/flight.hxx +++ b/src/FDM/flight.hxx @@ -440,6 +440,14 @@ public: //perform initializion that is common to all FDM's void common_init(); + // Makes possibly multiple calls of fn(ref, ...) with pairs of property + // paths that are associated in the FDM. Default implementation does + // nothing. Used by the Highlight subsystem. + virtual void property_associations( + void* ref, + void (*fn)(void* ref, const std::string& from, const std::string& to) + ); + // Positions virtual void set_Latitude(double lat); // geocentric virtual void set_Longitude(double lon); diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index 21289ecf7..32b15184c 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES PUIFileDialog.cxx MouseCursor.cxx MessageBox.cxx + Highlight.cxx ) set(HEADERS @@ -43,6 +44,7 @@ set(HEADERS PUIFileDialog.hxx MouseCursor.hxx MessageBox.hxx + Highlight.hxx ) if(WIN32) diff --git a/src/GUI/FGPUIDialog.cxx b/src/GUI/FGPUIDialog.cxx index fe880da07..8b6eee2d3 100644 --- a/src/GUI/FGPUIDialog.cxx +++ b/src/GUI/FGPUIDialog.cxx @@ -944,7 +944,10 @@ FGPUIDialog::makeObject(SGPropertyNode* props, int parentWidth, int parentHeight return obj; } else if (type == "property-list") { - PropertyList* obj = new PropertyList(x, y, x + width, y + height, globals->get_props()); + PropertyList* obj = new PropertyList(x, y, x + width, y + height, + globals->get_props(), + props->getBoolValue("readonly", false) + ); if (presetSize) obj->setSize(width, height); setupObject(obj, props); diff --git a/src/GUI/Highlight.cxx b/src/GUI/Highlight.cxx new file mode 100644 index 000000000..9fc1bd658 --- /dev/null +++ b/src/GUI/Highlight.cxx @@ -0,0 +1,666 @@ +#include "Highlight.hxx" +#include "simgear/misc/strutils.hxx" + +#include +#include
+#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + + +/* Returns translated string for specified menu/menuitem name. */ +static std::string s_get_menu_name(SGPropertyNode* node) +{ + /* We use 'label' if available, otherwise we translate 'name' using + sim/intl/locale/... */ + std::string name = node->getStringValue("label", ""); + if (name != "") return name; + name = node->getStringValue("name"); + return globals->get_props()->getStringValue("sim/intl/locale/strings/menu/" + name, name.c_str()); +} + +std::string HighlightMenu::description() const +{ + std::string ret = std::to_string(this->menu) + "/" + std::to_string(this->item) + ": "; + SGPropertyNode* menubar = globals->get_props()->getNode("sim/menubar/default"); + if (menubar) + { + SGPropertyNode* menu = menubar->getChild("menu", this->menu); + if (menu) + { + SGPropertyNode* item = menu->getChild("item", this->item); + if (item) + { + ret += s_get_menu_name(menu) + '/' + s_get_menu_name(item); + } + } + } + return ret; +} + +static bool operator< (const HighlightMenu& a, const HighlightMenu& b) +{ + if (a.menu != b.menu) return a.menu < b.menu; + return a.item < b.item; +} + +struct NameValue +{ + NameValue(const std::string& name, const std::string& value) + : name(name), value(value) + {} + std::string name; + std::string value; + bool operator< (const NameValue& rhs) const + { + if (name != rhs.name) return name < rhs.name; + return value < rhs.value; + } +}; + +static std::map s_property_to_info; + +static std::map, std::set> s_node_to_properties; +static std::map> s_dialog_to_properties; +static std::map> s_keypress_to_properties; +static std::map> s_menu_to_properties; + +static std::map s_menu_to_dialog; +static std::map> s_dialog_to_menus; + +static std::map> s_property_to_properties; +static std::map> s_property_from_properties; + +static std::set s_property_tree_items; + +static SGPropertyNode_ptr s_prop_enabled = nullptr; +static bool s_output_stats = false; + +static std::unique_ptr s_node_highlighting; + +std::unique_ptr s_fdm_initialised_listener; + + +/* Handles highlighting of individual nodes by changing their StateSet to +our internal StateSet. Also changes our StateSet's material in response to +sim/highlighting/material/. */ +struct NodeHighlighting : SGPropertyChangeListener +{ + NodeHighlighting() + : + m_node(globals->get_props()->getNode("sim/highlighting/material", true /*create*/)) + { + /* Create highlight StateSet. Based on + simgear/simgear/scene/model/SGPickAnimation.cxx:sharedHighlightStateSet(). + */ + m_state_set = new osg::StateSet; + + osg::Texture2D* white = simgear::StateAttributeFactory::instance()->getWhiteTexture(); + m_state_set->setTextureAttributeAndModes( + 0, + white, + osg::StateAttribute::ON + | osg::StateAttribute::OVERRIDE + | osg::StateAttribute::PROTECTED + ); + + osg::PolygonOffset* polygonOffset = new osg::PolygonOffset; + polygonOffset->setFactor(-1); + polygonOffset->setUnits(-1); + m_state_set->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE); + m_state_set->setMode( + GL_POLYGON_OFFSET_LINE, + osg::StateAttribute::ON + | osg::StateAttribute::OVERRIDE + ); + + osg::PolygonMode* polygonMode = new osg::PolygonMode; + polygonMode->setMode( + osg::PolygonMode::FRONT_AND_BACK, + osg::PolygonMode::FILL + ); + m_state_set->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE); + + osg::Material* material = new osg::Material; + material->setColorMode(osg::Material::OFF); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0)); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0)); + material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0)); + material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0)); + m_state_set->setAttribute( + material, + osg::StateAttribute::OVERRIDE + | osg::StateAttribute::PROTECTED + ); + // The default shader has a colorMode uniform that mimics the + // behavior of Material color mode. + + osg::Uniform* colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode"); + colorModeUniform->set(0); // MODE_OFF + colorModeUniform->setDataVariance(osg::Object::STATIC); + m_state_set->addUniform( + colorModeUniform, + osg::StateAttribute::OVERRIDE + | osg::StateAttribute::ON + ); + + /* Initialise highlight material properties to defaults if not already + set. */ + // XXX Alpha < 1.0 in the diffuse material value is a signal to the + // default shader to take the alpha value from the material value + // and not the glColor. In many cases the pick animation geometry is + // transparent, so the outline would not be visible without this hack. + set_rgba_default(m_node, "ambient", 0, 0, 0, 1); + set_rgba_default(m_node, "diffuse", 0.5, 0.5, 0.5, .95); + set_rgba_default(m_node, "emission", 0.75, 0.375, 0, 1); + set_rgba_default(m_node, "specular", 0.0, 0.0, 0.0, 0); + + /* Listen for changes to highlight material properties. */ + m_node->addChangeListener(this, true /*initial*/); + } + + static void set_rgba_default(SGPropertyNode* root, const std::string& path, + double red, double green, double blue, double alpha) + { + set_property_default(root, path + "/red", red); + set_property_default(root, path + "/green", green); + set_property_default(root, path + "/blue", blue); + set_property_default(root, path + "/alpha", alpha); + } + + // Sets property value if property does not exist. + static void set_property_default(SGPropertyNode* root, const std::string& path, double default_value) + { + SGPropertyNode* property = root->getNode(path, false /*create*/); + if (!property) root->setDoubleValue(path, default_value); + } + + virtual void valueChanged(SGPropertyNode* node) + { + assert(m_state_set); + osg::StateAttribute* material0 = m_state_set->getAttribute(osg::StateAttribute::MATERIAL); + osg::Material* material = dynamic_cast(material0); + assert(material); + + double red; + double green; + double blue; + double alpha; + + red = m_node->getDoubleValue("ambient/red"); + green = m_node->getDoubleValue("ambient/green"); + blue = m_node->getDoubleValue("ambient/blue"); + alpha = m_node->getDoubleValue("ambient/alpha"); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(red, green, blue, alpha)); + + red = m_node->getDoubleValue("diffuse/red"); + green = m_node->getDoubleValue("diffuse/green"); + blue = m_node->getDoubleValue("diffuse/blue"); + alpha = m_node->getDoubleValue("diffuse/alpha"); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(red, green, blue, alpha)); + + red = m_node->getDoubleValue("emission/red"); + green = m_node->getDoubleValue("emission/green"); + blue = m_node->getDoubleValue("emission/blue"); + alpha = m_node->getDoubleValue("emission/alpha"); + material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(red, green, blue, alpha)); + + red = m_node->getDoubleValue("ambient/red"); + green = m_node->getDoubleValue("ambient/green"); + blue = m_node->getDoubleValue("ambient/blue"); + alpha = m_node->getDoubleValue("ambient/alpha"); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(red, green, blue, alpha)); + + red = m_node->getDoubleValue("specular/red"); + green = m_node->getDoubleValue("specular/green"); + blue = m_node->getDoubleValue("specular/blue"); + alpha = m_node->getDoubleValue("specular/alpha"); + material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(red, green, blue, alpha)); + } + + /* Highlight/un-highlight the specified node. */ + int highlight(osg::Node* node, bool highlight) + { + int ret = 0; + osg::Group* group = node->asGroup(); + if (!group) return 0; + auto it_group_stateset = m_group_to_old_state_set.find(group); + if (it_group_stateset == m_group_to_old_state_set.end() && highlight) + { + m_group_to_old_state_set[group] = group->getStateSet(); + group->setStateSet(m_state_set.get()); + ret += 1; + } + if (it_group_stateset != m_group_to_old_state_set.end() && !highlight) + { + group->setStateSet(it_group_stateset->second); + m_group_to_old_state_set.erase(it_group_stateset); + ret += 1; + } + return ret; + } + + /* Un-highlight all nodes. */ + int clear_all() + { + int ret = 0; + for(;;) + { + if (m_group_to_old_state_set.empty()) break; + auto& group_stateset = *m_group_to_old_state_set.begin(); + highlight(group_stateset.first, false); + ret += 1; + } + return ret; + } + + ~NodeHighlighting() + { + m_node->removeChangeListener(this); + } + + SGPropertyNode_ptr m_node; + + /* We keep track of osg::StateSet's for groups that we have highlighted so + that we can turn highlighting off by swapping the original stateset back + in. */ + std::map, osg::ref_ptr> m_group_to_old_state_set; + + /* The stateset that we use to highlight nodes. */ + osg::ref_ptr m_state_set; +}; + + +Highlight::Highlight() +{ +} + +Highlight::~Highlight() +{ +} + +void Highlight::shutdown() +{ + s_property_to_info.clear(); + + s_node_to_properties.clear(); + s_dialog_to_properties.clear(); + s_keypress_to_properties.clear(); + s_menu_to_properties.clear(); + + s_menu_to_dialog.clear(); + s_dialog_to_menus.clear(); + + s_property_to_properties.clear(); + + s_property_tree_items.clear(); + s_prop_enabled.reset(); +} + +void Highlight::init() +{ + s_node_highlighting.reset(new NodeHighlighting); +} + +/* Waits until sim/fdm-initialized is true and then gets property associations +from the FDM using FGInterface::property_associations(). */ +struct FdmInitialisedListener : SGPropertyChangeListener +{ + FdmInitialisedListener() + : + m_fdm_initialised(globals->get_props()->getNode("sim/fdm-initialized", true /*create*/)) + { + m_fdm_initialised->addChangeListener(this, true /*initial*/); + } + static void property_associations_callback(void* ref, const std::string& from, const std::string& to) + { + SG_LOG(SG_GENERAL, SG_DEBUG, "fdm property association: " << from << " => " << to); + Highlight* highlight = (Highlight*) ref; + highlight->add_property_property(from, to); + } + void valueChanged(SGPropertyNode* node) override + { + if (m_fdm_initialised->getBoolValue()) + { + SG_LOG(SG_GENERAL, SG_DEBUG, "Getting property associations from FDM"); + Highlight* highlight = globals->get_subsystem(); + FDMShell* fdmshell = (FDMShell*) globals->get_subsystem("flight"); + FGInterface* fginterface = fdmshell->getInterface(); + assert(fginterface); + fginterface->property_associations(highlight, property_associations_callback); + s_fdm_initialised_listener.reset(); + } + } + ~FdmInitialisedListener() + { + globals->get_props()->getNode("sim/fdm-initialized")->removeChangeListener(this); + } + SGPropertyNode_ptr m_fdm_initialised; +}; + +void Highlight::bind() +{ + s_prop_enabled = globals->get_props()->getNode("sim/highlighting/enabled"); + globals->get_props()->setStringValue("sim/highlighting/current", ""); + globals->get_props()->setStringValue("sim/highlighting/current-ptr", "/sim/highlighting/current"); + + /* Get property associations from FDM when sim/fdm-initialized becomes true. */ + s_fdm_initialised_listener.reset(new FdmInitialisedListener); +} + +void Highlight::reinit() +{ +} + +void Highlight::unbind() +{ +} + +void Highlight::update(double dt) +{ +} + +/* Avoid ambiguities caused by possible use of "[0]" within property paths. */ +static std::string canonical(const std::string property) +{ + return simgear::strutils::replace(property, "[0]", ""); +} + + +int Highlight::highlight_nodes(osg::Node* node) +{ + if (!s_output_stats) + { + s_output_stats = true; + SG_LOG(SG_GENERAL, SG_DEBUG, "Sizes of internal maps:" + << " s_property_to_info=" << s_property_to_info.size() + << " s_node_to_properties=" << s_node_to_properties.size() + << " s_dialog_to_properties=" << s_dialog_to_properties.size() + << " s_keypress_to_properties=" << s_keypress_to_properties.size() + << " s_menu_to_properties=" << s_menu_to_properties.size() + << " s_menu_to_dialog=" << s_menu_to_dialog.size() + << " s_dialog_to_menus=" << s_dialog_to_menus.size() + << " s_property_to_properties=" << s_property_to_properties.size() + << " s_property_from_properties=" << s_property_from_properties.size() + << " s_property_tree_items=" << s_property_tree_items.size() + ); + } + if (!s_node_highlighting) return 0; + + s_node_highlighting->clear_all(); + + int num_props = 0; + + /* We build up a list of name:value items such as: + + 'dialog': joystick-config + 'menu': 0/6: File/Joystick Configuration + 'property': /controls/flight/rudder + 'property': /surface-positions/rudder-pos-norm + + These items are written into /sim/highlighting/current/. For items with + name='property', we highlight nodes which are animated by the specified + property. */ + std::set items; + + if (s_prop_enabled->getBoolValue()) + { + /* As well as itself, we also highlight other nodes by following + a limited part of the tree of property dependencies: + + property3 + -> property1 + -> + -> other nodes + -> property2 + -> other nodes + -> other nodes + -> property4 + -> other nodes + */ + for (auto& property1: Highlight::find_node_properties(node)) + { + /* animates . */ + items.insert(NameValue("property", property1)); + + for (auto& property2: Highlight::find_property_to_properties(property1)) + { + /* is set by (which animates ). */ + items.insert(NameValue("property", property2)); + } + + for (auto& property3: Highlight::find_property_from_properties(property1)) + { + /* sets (which animates ). */ + items.insert(NameValue("property", property3)); + + for (auto& property4: Highlight::find_property_to_properties(property3)) + { + /* is set by (which also + sets , which animates ). */ + items.insert(NameValue("property", property4)); + } + } + } + + /* Highlight all nodes animated by properties in , and add + nodes, dialogs, menus and keypress info to . */ + for (auto& nv: items) + { + const std::string& property = nv.value; + SG_LOG(SG_GENERAL, SG_DEBUG, "Looking at property=" << property); + const HighlightInfo& info = Highlight::find_property_info(property); + for (auto& node: info.nodes) + { + num_props += s_node_highlighting->highlight(node, true); + } + for (auto& dialog: info.dialogs) + { + items.insert(NameValue("dialog", dialog)); + for (auto& menu: Highlight::find_menu_from_dialog(dialog)) + { + items.insert(NameValue("menu", menu.description())); + } + } + for (auto& keypress: info.keypresses) + { + items.insert(NameValue("keypress", keypress)); + } + for (auto& menu: info.menus) + { + items.insert(NameValue("menu", menu.description())); + } + } + } + else + { + num_props = -1; + } + + if (items.empty()) + { + /* Create dummy item to allow property viewer to be opened on + sim/highlighting/current even if it would otherwise be currently empty. + */ + items.insert(NameValue("dummy", "")); + } + + /* Update property tree representation of currently-highlighted properties + and associated dialogs, keypresses and menus, so that it matches + . We do this by calculating what items need adding or removing from + the property tree representation */ + std::set properties_new; + std::set properties_removed; + std::set_difference( + items.begin(), items.end(), + s_property_tree_items.begin(), s_property_tree_items.end(), + std::inserter(properties_new, properties_new.begin()) + ); + std::set_difference( + s_property_tree_items.begin(), s_property_tree_items.end(), + items.begin(), items.end(), + std::inserter(properties_removed, properties_removed.begin()) + ); + SGPropertyNode* current = globals->get_props()->getNode("sim/highlighting/current", true /*create*/); + for (int pos=0; posnChildren(); ++pos) + { + SGPropertyNode* child = current->getChild(pos); + auto nv = NameValue(child->getNameString(), child->getStringValue()); + if (properties_removed.find(nv) != properties_removed.end()) + { + current->removeChild(pos); + pos -= 1; + } + } + for (auto& name_value: properties_new) + { + current->addChild(name_value.name)->setStringValue(name_value.value); + } + + /* Remember the current items for next time. */ + std::swap(items, s_property_tree_items); + + return num_props; +} + +/* Functions that interrogate our internal data. + + +We use these to avoid adding multiple empty items to our std::maps. +*/ +static const HighlightInfo info_empty; +static const std::set set_string_empty; +static const std::set set_menu_empty; + +const HighlightInfo& Highlight::find_property_info(const std::string& property) +{ + std::string property2 = canonical(property); + auto it = s_property_to_info.find(property2); + if (it == s_property_to_info.end()) return info_empty; + return it->second; +} + +const std::set& Highlight::find_node_properties(osg::Node* node) +{ + auto it = s_node_to_properties.find(node); + if (it == s_node_to_properties.end()) return set_string_empty; + return it->second; +} + +const std::set& Highlight::find_dialog_properties(const std::string& dialog) +{ + auto it = s_dialog_to_properties.find(dialog); + if (it == s_dialog_to_properties.end()) return set_string_empty; + return it->second; +} + +const std::set& Highlight::find_keypress_properties(const std::string& keypress) +{ + auto it = s_keypress_to_properties.find(keypress); + if (it == s_keypress_to_properties.end()) return set_string_empty; + return it->second; +} + +const std::set& Highlight::find_menu_properties(const HighlightMenu& menu) +{ + auto it = s_menu_to_properties.find(menu); + if (it == s_menu_to_properties.end()) return set_string_empty; + return it->second; +} + +const std::set& Highlight::find_property_to_properties(const std::string& property) +{ + std::string property2 = canonical(property); + auto it = s_property_to_properties.find(property2); + if (it == s_property_to_properties.end()) return set_string_empty; + return it->second; +} + +const std::set& Highlight::find_property_from_properties(const std::string& property) +{ + std::string property2 = canonical(property); + auto it = s_property_from_properties.find(property2); + if (it == s_property_from_properties.end()) return set_string_empty; + return it->second; +} + +const std::set& Highlight::find_menu_from_dialog(const std::string& dialog) +{ + auto it = s_dialog_to_menus.find(dialog); + if (it == s_dialog_to_menus.end()) return set_menu_empty; + return it->second; +} + + +/* Functions that populate our internal data. */ + +void Highlight::add_property_node(const std::string& property, osg::ref_ptr node) +{ + std::string property2 = canonical(property); + s_property_to_info[property2].nodes.insert(node); + if (s_node_to_properties[node].insert(property2).second) + SG_LOG(SG_INPUT, SG_DEBUG, "node=" << node.get() << " property=" << property2); +} + +void Highlight::add_property_dialog(const std::string& property, const std::string& dialog) +{ + std::string property2 = canonical(property); + s_property_to_info[property2].dialogs.insert(dialog); + if (s_dialog_to_properties[dialog].insert(property2).second) + SG_LOG(SG_INPUT, SG_DEBUG, "dialog=" << dialog << " property=" << property2); +} + +void Highlight::add_property_keypress(const std::string& property, const std::string& keypress) +{ + std::string property2 = canonical(property); + s_property_to_info[property2].keypresses.insert(keypress); + if (s_keypress_to_properties[keypress].insert(property2).second) + SG_LOG(SG_INPUT, SG_DEBUG, "keypress=" << keypress << " property=" << property2); +} + +void Highlight::add_property_menu(HighlightMenu menu, const std::string& property) +{ + std::string property2 = canonical(property); + s_property_to_info[property2].menus.insert(menu); + if (s_menu_to_properties[menu].insert(property2).second) + SG_LOG(SG_INPUT, SG_DEBUG, "menu=(" << menu.menu << " " << menu.item << ") property=" << property2); +} + +void Highlight::add_menu_dialog(HighlightMenu menu, const std::string& dialog) +{ + s_menu_to_dialog[menu] = dialog; + if (s_dialog_to_menus[dialog].insert(menu).second) + SG_LOG(SG_INPUT, SG_DEBUG, "menu (" << menu.menu << " " << menu.item << ") dialog=" << dialog); +} + +void Highlight::add_property_property(const std::string& from0, const std::string& to0) +{ + std::string from = canonical(from0); + std::string to = canonical(to0); + if (from == to) return; + bool is_new = false; + if (s_property_to_properties[from].insert(to).second) is_new = true; + if (s_property_from_properties[to].insert(from).second) is_new = true; + if (is_new) + { + // Add transitive associations. + for (auto& toto: s_property_to_properties[to]) + { + Highlight::add_property_property(from, toto); + } + for (auto& fromfrom: s_property_from_properties[from]) + { + Highlight::add_property_property(fromfrom, to); + } + } +} diff --git a/src/GUI/Highlight.hxx b/src/GUI/Highlight.hxx new file mode 100644 index 000000000..38591bb10 --- /dev/null +++ b/src/GUI/Highlight.hxx @@ -0,0 +1,114 @@ +#pragma once + +#include +#include + +#include +#include + +/* Represents a menu item. */ +struct HighlightMenu +{ + HighlightMenu(int menu, int item) : menu(menu), item(item) + {} + int menu; /* Number of menu in the menubar, e.g. 0 is 'File'. */ + int item; /* Number of item in the menu. */ + std::string description() const; /* E.g. "0/6: File/Joystick Configuration". */ +}; + +/* Information about OSG nodes, dialogs etc that are associated with a +particular property. */ +struct HighlightInfo +{ + /* OSG Nodes that are animated by the property. */ + std::set> nodes; + + /* Dialogues that modify the property.*/ + std::set dialogs; + + /* Keypresses that modify the property. */ + std::set keypresses; + + /* Menu items that modify the property. */ + std::set menus; +}; + +struct Highlight : SGSubsystem +{ + Highlight(); + ~Highlight(); + + void bind() override; + void init() override; + void reinit() override; + void shutdown() override; + void unbind() override; + void update(double dt) override; + + static const char* staticSubsystemClassId() { return "reflect"; } + + /* If specified node is animated, highlights it and other nodes + that are animated by the same or related properties. Also updates + /sim/highlighting/current to contain information about associated dialogs, + menus, and keypresses. + + Returns the number of properties found. Returns -1 if highlighting is not + currently enabled. */ + int highlight_nodes(osg::Node* node); + + /* Returns information about nodes and UI elements that are associated with a + specific property. */ + const HighlightInfo& find_property_info(const std::string& property); + + + /* Below are individual functions that return properties that are associated + with a particular node or UI element. */ + + /* Returns list of properties that are used to animate the specified OSG node. + */ + const std::set& find_node_properties(osg::Node* node); + + /* Returns list of properties affected by specified dialog. */ + const std::set& find_dialog_properties(const std::string& dialog); + + /* Returns list of properties affected by specified keypress. */ + const std::set& find_keypress_properties(const std::string& keypress); + + /* Returns list of properties affected by specified menu. */ + const std::set& find_menu_properties(const HighlightMenu& menu); + + /* Returns list of properties that are influenced by the specified property, + /e.g. if is controls/flight/rudder, the returned set could contain + /surface-positions/rudder-pos-norm. */ + const std::set& find_property_to_properties(const std::string& property); + + /* Returns list of properties that influence the specified property, e.g. + if is /surface-positions/rudder-pos-norm, the returned set could + contain /controls/flight/rudder. */ + const std::set& find_property_from_properties(const std::string& property); + + /* Returns list of menus that open the specified dialog. */ + const std::set& find_menu_from_dialog(const std::string& dialog); + + + /* Below are functions that are used to set up associations. */ + + /* Should be called if is animated using . */ + void add_property_node(const std::string& property, osg::ref_ptr node); + + /* Should be called if affects . */ + void add_property_dialog(const std::string& property, const std::string& dialog); + + /* Should be called if affects . */ + void add_property_keypress(const std::string& property, const std::string& keypress); + + /* Should be called if affects . */ + void add_property_menu(HighlightMenu menu, const std::string& property); + + /* Should be called if opens . */ + void add_menu_dialog(HighlightMenu menu, const std::string& dialog); + + /* Should be called if two properties are associated, for example YASim + associates /controls/flight/flaps with /surface-positions/flap-pos-norm. */ + void add_property_property(const std::string& property1, const std::string& property2); +}; diff --git a/src/GUI/new_gui.cxx b/src/GUI/new_gui.cxx index c54e7b2ac..c0d0bff3b 100644 --- a/src/GUI/new_gui.cxx +++ b/src/GUI/new_gui.cxx @@ -39,6 +39,8 @@ #include "FGFontCache.hxx" #include "FGColor.hxx" +#include "Highlight.hxx" + // ignore the word Navaid here, it's a DataCache #include @@ -64,6 +66,44 @@ NewGUI::~NewGUI () delete it->second; } +// Recursively finds all nodes called and appends the string values of +// these nodes to . +// +static void findAllLeafValues(SGPropertyNode* node, const std::string& leaf, std::vector& out) +{ + const char* name = node->getName(); + if (name == leaf) { + out.push_back(node->getStringValue()); + } + for (int i=0; inChildren(); ++i) { + findAllLeafValues(node->getChild(i), leaf, out); + } +} + + +/* Registers menu-dialog associations with Highlight::add_menu_dialog(). */ +static void scanMenus() +{ + /* We find all nodes called 'dialog-name' in + sim/menubar/default/menu[]/item[]. */ + SGPropertyNode* menubar = globals->get_props()->getNode("sim/menubar/default"); + assert(menubar); + Highlight* highlight = globals->get_subsystem(); + for (int menu_p=0; menu_pnChildren(); ++menu_p) { + SGPropertyNode* menu = menubar->getChild(menu_p); + if (menu->getNameString() != "menu") continue; + for (int item_p=0; item_pnChildren(); ++item_p) { + SGPropertyNode* item = menu->getChild(item_p); + if (item->getNameString() != "item") continue; + std::vector dialog_names; + findAllLeafValues(item, "dialog-name", dialog_names); + for (auto dialog_name: dialog_names) { + highlight->add_menu_dialog(HighlightMenu(menu->getIndex(), item->getIndex()), dialog_name); + } + } + } +} + void NewGUI::init () { @@ -96,6 +136,7 @@ NewGUI::init () // Fix for http://code.google.com/p/flightgear-bugs/issues/detail?id=947 fgGetNode("sim/menubar")->setAttribute(SGPropertyNode::PRESERVE, true); _menubar->init(); + scanMenus(); } void @@ -384,6 +425,7 @@ NewGUI::newDialog (SGPropertyNode* props) } } + void NewGUI::readDir (const SGPath& path) { @@ -396,7 +438,37 @@ NewGUI::readDir (const SGPath& path) flightgear::NavDataCache* cache = flightgear::NavDataCache::instance(); flightgear::NavDataCache::Transaction txn(cache); + Highlight* highlight = globals->get_subsystem(); for (SGPath xmlPath : dir.children(simgear::Dir::TYPE_FILE, ".xml")) { + + SGPropertyNode_ptr props = new SGPropertyNode; + SGPropertyNode *nameprop = nullptr; + std::string name; + + /* Always parse the dialog even if cache->isCachedFileModified() says + it is cached, so that we can register property-dialog associations with + Highlight::add_property_dialog(). */ + try { + readProperties(xmlPath, props); + } catch (const sg_exception &) { + SG_LOG(SG_INPUT, SG_ALERT, "Error parsing dialog " << xmlPath); + props.reset(); + } + if (props) { + nameprop = props->getNode("name"); + if (nameprop) { + name = nameprop->getStringValue(); + // Look for 'property' nodes within the dialog. (could + // also look for "dialog-name" to catch things like + // dialog-show.) + std::vector property_paths; + findAllLeafValues(props, "property", property_paths); + for (auto property_path: property_paths) { + highlight->add_property_dialog(property_path, name); + } + } + } + if (!cache->isCachedFileModified(xmlPath)) { // cached, easy string name = cache->readStringProperty(xmlPath.utf8Str()); @@ -404,32 +476,27 @@ NewGUI::readDir (const SGPath& path) continue; } - // we need to parse the actual XML - SGPropertyNode_ptr props = new SGPropertyNode; - try { - readProperties(xmlPath, props); - } catch (const sg_exception &) { + // we need to parse the actual XML + if (!props) { SG_LOG(SG_INPUT, SG_ALERT, "Error parsing dialog " << xmlPath); continue; } - SGPropertyNode *nameprop = props->getNode("name"); if (!nameprop) { SG_LOG(SG_INPUT, SG_WARN, "dialog " << xmlPath << " has no name; skipping."); continue; } - string name = nameprop->getStringValue(); _dialog_names[name] = xmlPath; - // update cached values - if (!cache->isReadOnly()) { - cache->stampCacheFile(xmlPath); - cache->writeStringProperty(xmlPath.utf8Str(), name); - } + // update cached values + if (!cache->isReadOnly()) { + cache->stampCacheFile(xmlPath); + cache->writeStringProperty(xmlPath.utf8Str(), name); + } } // of directory children iteration txn.commit(); -} +} //////////////////////////////////////////////////////////////////////// // Style handling. //////////////////////////////////////////////////////////////////////// diff --git a/src/GUI/property_list.cxx b/src/GUI/property_list.cxx index a25fa9edb..26f8e27f2 100644 --- a/src/GUI/property_list.cxx +++ b/src/GUI/property_list.cxx @@ -157,18 +157,18 @@ static void sanitize(stdString& s) -PropertyList::PropertyList(int minx, int miny, int maxx, int maxy, SGPropertyNode *start) : +PropertyList::PropertyList(int minx, int miny, int maxx, int maxy, SGPropertyNode *start, bool readonly) : puaList(minx, miny, maxx, maxy, short(0), 20), GUI_ID(FGCLASS_PROPERTYLIST), _curr(start), _return(0), _entries(0), _num_entries(0), - _verbose(false) - + _verbose(false), + _readonly(readonly) { _list_box->setUserData(this); - _list_box->setCallback(handle_select); + if (!_readonly) _list_box->setCallback(handle_select); _list_box->setValue(0); update(); } @@ -265,7 +265,7 @@ void PropertyList::update(bool restore_pos) _num_entries = (int)_curr->nChildren(); // instantiate string objects and add [.] and [..] for subdirs - if (!_curr->getParent()) { + if (_readonly || !_curr->getParent()) { _entries = new char*[_num_entries + 1]; pi = 0; _dot_files = false; diff --git a/src/GUI/property_list.hxx b/src/GUI/property_list.hxx index e11d5bfe8..8e1780fb2 100644 --- a/src/GUI/property_list.hxx +++ b/src/GUI/property_list.hxx @@ -32,7 +32,9 @@ class PropertyList : public puaList, public SGPropertyChangeListener, public GUI_ID { public: - PropertyList(int minx, int miny, int maxx, int maxy, SGPropertyNode *); + // If is true, we don't show . or .. items and don't respond to + // mouse clicks or keypresses. + PropertyList(int minx, int miny, int maxx, int maxy, SGPropertyNode *root, bool readonly); ~PropertyList(); void update (bool restore_slider_pos = false); @@ -89,6 +91,7 @@ private: bool _dot_files; // . and .. pseudo-dirs currently shown? bool _verbose; // show SGPropertyNode flags std::string _return_path; + bool _readonly; }; diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index f6b39b9da..0baae79cc 100755 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -100,6 +100,7 @@ #include #include #include +#include #include #include #include @@ -988,6 +989,7 @@ void fgCreateSubsystems(bool duringReset) { // Initialize the property interpolator subsystem. Put into the INIT // group because the "nasal" subsystem may need it at GENERAL take-down. globals->add_subsystem("prop-interpolator", new FGInterpolator, SGSubsystemMgr::INIT); + globals->add_new_subsystem(SGSubsystemMgr::INIT); globals->add_subsystem("gui", new NewGUI, SGSubsystemMgr::INIT); } diff --git a/src/Model/acmodel.cxx b/src/Model/acmodel.cxx index 2e0ab6ee2..fed8ae9f1 100644 --- a/src/Model/acmodel.cxx +++ b/src/Model/acmodel.cxx @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include "acmodel.hxx" @@ -66,6 +68,80 @@ FGAircraftModel::~FGAircraftModel () shutdown(); } + +// Gathers information about all animated nodes and the properties that they +// depend on, and registers with Highlight::add_property_node(). +// +struct VisitorHighlight : osg::NodeVisitor +{ + VisitorHighlight() + { + m_highlight = globals->get_subsystem(); + setTraversalMode(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN); + } + std::string spaces() + { + return std::string(m_level*4, ' '); + } + virtual void apply(osg::Node& node) + { + SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << "node: " << node.libraryName() << "::" << node.className()); + m_level += 1; + traverse(node); + m_level -= 1; + } + virtual void apply(osg::Group& group) + { + // Parent nodes of are animated by properties in + // , so register the association between and + // these properties. + SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << "group: " << group.libraryName() << "::" << group.className()); + for (auto name: m_highlight_names) + { + m_highlight->add_property_node(name, &group); + } + m_level += 1; + traverse(group); + m_level -= 1; + } + virtual void apply( osg::Transform& node ) + { + SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << "transform: " + << node.libraryName() << "::" << node.className() + << ": " << typeid(node).name() + ); + + SGSharedPtr expression = TransformExpression(&node); + int num_added = 0; + if (expression) { + // All 's child nodes will be affected by so + // add all of 's properties to so + // that we can call Highlight::add_property_node() for each child + // node. + std::set properties; + expression->collectDependentProperties(properties); + SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << typeid(node).name() << ":"); + for (auto p: properties) { + SG_LOG(SG_GENERAL, SG_DEBUG, spaces() << " " << p->getPath(true)); + num_added += 1; + m_highlight_names.push_back(p->getPath(true /*simplify*/)); + } + } + + m_level += 1; + traverse(node); + m_level -= 1; + + // Remove the names we appended to earlier. + assert((int) m_highlight_names.size() >= num_added); + m_highlight_names.resize(m_highlight_names.size() - num_added); + } + unsigned int m_level = 0; // Only used to indent diagnostics. + std::vector m_highlight_names; + Highlight* m_highlight; +}; + + void FGAircraftModel::init () { @@ -142,6 +218,10 @@ FGAircraftModel::init () node = _interior->getSceneGraph(); globals->get_scenery()->get_interior_branch()->addChild(node); } + + // Register animated nodes and associated properties with Highlight. + VisitorHighlight visitor_highlight; + visitor_highlight.traverse(*node); } void diff --git a/src/Model/acmodel.hxx b/src/Model/acmodel.hxx index 96745ba20..05a477c99 100644 --- a/src/Model/acmodel.hxx +++ b/src/Model/acmodel.hxx @@ -12,6 +12,7 @@ #include #include // for SGSubsystem +#include // Don't pull in the headers, since we don't need them here. diff --git a/src/Viewer/renderer.cxx b/src/Viewer/renderer.cxx index 419c01697..d2489fcbd 100644 --- a/src/Viewer/renderer.cxx +++ b/src/Viewer/renderer.cxx @@ -94,6 +94,7 @@ #include #include #include +#include #include #include @@ -755,6 +756,7 @@ FGRenderer::setupView( void ) } // Update all Visuals (redraws anything graphics related) +// Called every frame. void FGRenderer::update( ) { if (!_position_finalized || !_scenery_loaded->getBoolValue()) @@ -994,6 +996,12 @@ PickList FGRenderer::pick(const osg::Vec2& windowPos) if (!computeIntersections(CameraGroup::getDefault(), windowPos, intersections)) return result; + + // We attempt to highlight nodes until Highlight::highlight_nodes() + // succeeds and returns +ve, or highlighting is disabled and it returns -1. + Highlight* highlight = globals->get_subsystem(); + int higlight_num_props = 0; + for (Intersections::iterator hit = intersections.begin(), e = intersections.end(); hit != e; @@ -1002,6 +1010,9 @@ PickList FGRenderer::pick(const osg::Vec2& windowPos) osg::NodePath::const_reverse_iterator npi; for (npi = np.rbegin(); npi != np.rend(); ++npi) { + if (!higlight_num_props) { + higlight_num_props = highlight->highlight_nodes(*npi); + } SGSceneUserData* ud = SGSceneUserData::getSceneUserData(*npi); if (!ud || (ud->getNumPickCallbacks() == 0)) continue;