1
0
Fork 0

Added highlighting system.

If /sim/highlighting/enabled is true, we highlight animated objects under the
pointer, and also highlight other objects that are animated by the same or
related properties.

The intent here is to be able to give a visual indication of what cockpit
controls do or what cockpit controls affect particular aircraft objects - for
example moving the pointer over the flaps will highlight the flaps and also
highlight any controls or rotary indicators in the cockpit that are associated
with the flaps.

To make this work, we have to discover associations between properties. This
is currently done for YASim (e.g. associations between /controls/flight/flaps
and /surface-positions/flap-pos-norm) and autopilot filters (e.g. with
the 777, digital filters associate /controls/flight/rudder-nul with
/fcs/fbw/yaw/rudder-ratio-out). We don't currently gather associations between
properties in JSBSim

We also detect associations between dialogs, menus and keypresses and
properties, which is used to populate /sim/highlighting/current with
information about dialogs, menus and keypresses that are associated with the
currently highlighted nodes' properties.

Details:

src/GUI/Highlight.cxx
src/GUI/Highlight.hxx
src/GUI/CMakeLists.txt
src/Main/fg_init.cxx
    New subsystem called 'highlight', with support for registering and
    recovering links between menus, dialogs, keypresses and OSG node
    animations.

    Provides a function Highlight::highlight_nodes() which highlights
    related nodes using internal NodeHighlighting class, and populates
    /sim/highlighting/current with information about related dialogs, menus and
    keypresses.

    The NodeHighlighting class works by making nodes use an alternative
    StateSet which shows up as a distinct material on screen. We remember each
    highlighted node's original StateSet so that we can un-highlight. We update
    the material parameters using a listener for /sim/highlighting/material,
    which allows some control over the appearence of highlighted nodes.

src/FDM/flight.cxx
src/FDM/flight.hxx
    Added virtual method FGInterface::property_associations() which returns
    property associations from the FDM. Default implementation returns empty
    set. Implemented in YASim, but not (yet) in JSBSim. Uses a simple function
    pointer at the moment to avoid requring FDMs to use recent C++ features.

src/FDM/YASim/FGFDM.cpp
src/FDM/YASim/FGFDM.hpp
src/FDM/YASim/YASim.cxx
src/FDM/YASim/YASim.hxx
    Gathers information about property associations on startup such as
    /controls/flight/flaps => /surface-positions/flap-pos-norm, then
    YASim::property_associations() overrides default implementation to return
    these associations.

src/Autopilot/analogcomponent.cxx
src/Autopilot/analogcomponent.hxx
src/Autopilot/digitalfilter.cxx
src/Autopilot/inputvalue.cxx
src/Autopilot/inputvalue.hxx
    Filters now gather information about their input/output properties and
    register with Highlight::add_property_property(). For example this makes
    highlighting work on the 777, where pilot controls affect control surfaces
    only via filters.

src/GUI/new_gui.cxx
    Scan menus, keypresses and dialogs and register associations with
    Highlight::add_*().

src/GUI/property_list.cxx
src/GUI/property_list.hxx
src/GUI/FGPUIDialog.cxx
    Added <readonly> flag to property-list. If set, we don't show .. or . items
    and don't respond to mouse/keyboard.

    Used by fgdata's new Highlighting dialogue.

src/Model/acmodel.cxx
src/Model/acmodel.hxx
    Visit the user aircraft's scene graph when it is loaded, gathering
    information about osg::Node's that are animated by properties, and register
    these associations with Highlight::add_property_node().

src/Viewer/renderer.cxx
    When scanning for pick highlights, use Highlight::highlight_nodes() to
    highlight animated objects under the pointer and related objects.
This commit is contained in:
Julian Smith 2021-10-05 22:25:08 +01:00
parent 95ea8248c8
commit 1bafe15c4c
22 changed files with 1204 additions and 31 deletions

View file

@ -120,3 +120,11 @@ bool AnalogComponent::configure( SGPropertyNode& cfg_node,
return Component::configure(cfg_node, cfg_name, prop_root);
}
void AnalogComponent::collectDependentProperties(std::set<const SGPropertyNode*>& props) const
{
_valueInput.collectDependentProperties(props);
_referenceInput.collectDependentProperties(props);
_minInput.collectDependentProperties(props);
_maxInput.collectDependentProperties(props);
}

View file

@ -139,6 +139,12 @@ protected:
public:
const PeriodicalValue * getPeriodicalValue() const { return _periodical; }
/**
Add to <props> all properties that are used by this component. Similar to
SGExpression::collectDependentProperties().
*/
void collectDependentProperties(std::set<const SGPropertyNode*>& props) const;
};
inline void AnalogComponent::disabled( double dt )

View file

@ -26,6 +26,8 @@
//
#include "digitalfilter.hxx"
#include <GUI/Highlight.hxx>
#include <Main/globals.hxx>
#include <deque>
#include <algorithm>
@ -52,7 +54,7 @@ class DigitalFilterImplementation:
SGPropertyNode& prop_root ) = 0;
void setDigitalFilter( DigitalFilter * digitalFilter ) { _digitalFilter = digitalFilter; }
virtual void collectDependentProperties(std::set<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& 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<const SGPropertyNode*>& props) const
{
_amplitude.collectDependentProperties(props);
}
};
/* --------------------------------------------------------------------------------- */
@ -846,19 +904,36 @@ 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<const SGPropertyNode*> inputs;
_implementation->collectDependentProperties(inputs);
collectDependentProperties(inputs);
Highlight* highlight = globals->get_subsystem<Highlight>();
for (auto in: inputs) {
for (auto& out: _output_list) {
highlight->add_property_property(
in->getPath(true /*simplify*/),
out->getPath(true /*simplify*/)
);
}
}
return true;
}

View file

@ -225,3 +225,19 @@ double InputValue::get_value() const
return _abs ? fabs(value) : value;
}
void InputValue::collectDependentProperties(std::set<const SGPropertyNode*>& 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<const SGPropertyNode*>& props) const
{
for (auto& iv: *this) {
iv->collectDependentProperties(props);
}
}

View file

@ -109,6 +109,7 @@ public:
return _condition == NULL ? true : _condition->test();
}
void collectDependentProperties(std::set<const SGPropertyNode*>& props) const;
};
/**
@ -134,6 +135,8 @@ class InputValueList : public std::vector<InputValue_ptr> {
InputValue_ptr input = get_active();
return input == NULL ? _def : input->get_value();
}
void collectDependentProperties(std::set<const SGPropertyNode*>& props) const;
private:
double _def;

View file

@ -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:
//
// <control-input control="FLAP0" axis="/controls/flight/flaps"/>
// <control-output control="FLAP0" prop="/surface-positions/flap-pos-norm"/>
//
// When parsing <control-input> we associate FLAP0 with
// /controls/flight/flaps in _control_to_input_properties. Similarly
// when parsing <control-output> 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 <control-input> and <control-output>,
// 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)

View file

@ -7,6 +7,11 @@
#include "Airplane.hpp"
#include "Vector.hpp"
#include <map>
#include <set>
#include <string>
namespace yasim {
class Wing;
@ -27,9 +32,15 @@ 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 {
std::string prefix;
@ -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<std::string, std::set<std::string>> _control_to_input_properties;
std::map<std::string, std::set<std::string>> _control_to_output_properties;
std::map<std::string, std::set<std::string>> _property_to_properties;
};
}; // namespace yasim

View file

@ -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();

View file

@ -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();

View file

@ -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.

View file

@ -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);

View file

@ -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)

View file

@ -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);

666
src/GUI/Highlight.cxx Normal file
View file

@ -0,0 +1,666 @@
#include "Highlight.hxx"
#include "simgear/misc/strutils.hxx"
#include <Model/acmodel.hxx>
#include <Main/globals.hxx>
#include <FDM/fdm_shell.hxx>
#include <FDM/flight.hxx>
#include <simgear/scene/util/StateAttributeFactory.hxx>
#include <osg/Material>
#include <osg/PolygonMode>
#include <osg/PolygonOffset>
#include <osg/Texture2D>
#include <algorithm>
#include <map>
#include <string>
/* 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<std::string, HighlightInfo> s_property_to_info;
static std::map<osg::ref_ptr<osg::Node>, std::set<std::string>> s_node_to_properties;
static std::map<std::string, std::set<std::string>> s_dialog_to_properties;
static std::map<std::string, std::set<std::string>> s_keypress_to_properties;
static std::map<HighlightMenu, std::set<std::string>> s_menu_to_properties;
static std::map<HighlightMenu, std::string> s_menu_to_dialog;
static std::map<std::string, std::set<HighlightMenu>> s_dialog_to_menus;
static std::map<std::string, std::set<std::string>> s_property_to_properties;
static std::map<std::string, std::set<std::string>> s_property_from_properties;
static std::set<NameValue> s_property_tree_items;
static SGPropertyNode_ptr s_prop_enabled = nullptr;
static bool s_output_stats = false;
static std::unique_ptr<struct NodeHighlighting> s_node_highlighting;
std::unique_ptr<struct FdmInitialisedListener> 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<osg::Material*>(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<osg::Group>, osg::ref_ptr<osg::StateSet>> m_group_to_old_state_set;
/* The stateset that we use to highlight nodes. */
osg::ref_ptr<osg::StateSet> 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<Highlight>();
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<NameValue> items;
if (s_prop_enabled->getBoolValue())
{
/* As well as <node> itself, we also highlight other nodes by following
a limited part of the tree of property dependencies:
property3
-> property1
-> <node>
-> other nodes
-> property2
-> other nodes
-> other nodes
-> property4
-> other nodes
*/
for (auto& property1: Highlight::find_node_properties(node))
{
/* <property1> animates <node>. */
items.insert(NameValue("property", property1));
for (auto& property2: Highlight::find_property_to_properties(property1))
{
/* <property2> is set by <property1> (which animates <node>). */
items.insert(NameValue("property", property2));
}
for (auto& property3: Highlight::find_property_from_properties(property1))
{
/* <property3> sets <property1> (which animates <node>). */
items.insert(NameValue("property", property3));
for (auto& property4: Highlight::find_property_to_properties(property3))
{
/* <property4> is set by <property3> (which also
sets <property1>, which animates <node>). */
items.insert(NameValue("property", property4));
}
}
}
/* Highlight all nodes animated by properties in <items>, and add
nodes, dialogs, menus and keypress info to <items>. */
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
<items>. We do this by calculating what items need adding or removing from
the property tree representation */
std::set<NameValue> properties_new;
std::set<NameValue> 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; pos<current->nChildren(); ++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<std::string> set_string_empty;
static const std::set<HighlightMenu> 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<HighlightMenu>& 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<osg::Node> 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);
}
}
}

114
src/GUI/Highlight.hxx Normal file
View file

@ -0,0 +1,114 @@
#pragma once
#include <simgear/structure/subsystem_mgr.hxx>
#include <osg/Node>
#include <string>
#include <set>
/* 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<osg::ref_ptr<osg::Node>> nodes;
/* Dialogues that modify the property.*/
std::set<std::string> dialogs;
/* Keypresses that modify the property. */
std::set<std::string> keypresses;
/* Menu items that modify the property. */
std::set<HighlightMenu> 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<std::string>& find_node_properties(osg::Node* node);
/* Returns list of properties affected by specified dialog. */
const std::set<std::string>& find_dialog_properties(const std::string& dialog);
/* Returns list of properties affected by specified keypress. */
const std::set<std::string>& find_keypress_properties(const std::string& keypress);
/* Returns list of properties affected by specified menu. */
const std::set<std::string>& find_menu_properties(const HighlightMenu& menu);
/* Returns list of properties that are influenced by the specified property,
/e.g. if <property> is controls/flight/rudder, the returned set could contain
/surface-positions/rudder-pos-norm. */
const std::set<std::string>& find_property_to_properties(const std::string& property);
/* Returns list of properties that influence the specified property, e.g.
if <property> is /surface-positions/rudder-pos-norm, the returned set could
contain /controls/flight/rudder. */
const std::set<std::string>& find_property_from_properties(const std::string& property);
/* Returns list of menus that open the specified dialog. */
const std::set<HighlightMenu>& find_menu_from_dialog(const std::string& dialog);
/* Below are functions that are used to set up associations. */
/* Should be called if <node> is animated using <property>. */
void add_property_node(const std::string& property, osg::ref_ptr<osg::Node> node);
/* Should be called if <dialog> affects <property>. */
void add_property_dialog(const std::string& property, const std::string& dialog);
/* Should be called if <keypress> affects <property>. */
void add_property_keypress(const std::string& property, const std::string& keypress);
/* Should be called if <menu> affects <property>. */
void add_property_menu(HighlightMenu menu, const std::string& property);
/* Should be called if <menu> opens <dialog>. */
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);
};

View file

@ -39,6 +39,8 @@
#include "FGFontCache.hxx"
#include "FGColor.hxx"
#include "Highlight.hxx"
// ignore the word Navaid here, it's a DataCache
#include <Navaids/NavDataCache.hxx>
@ -64,6 +66,44 @@ NewGUI::~NewGUI ()
delete it->second;
}
// Recursively finds all nodes called <leaf> and appends the string values of
// these nodes to <out>.
//
static void findAllLeafValues(SGPropertyNode* node, const std::string& leaf, std::vector<std::string>& out)
{
const char* name = node->getName();
if (name == leaf) {
out.push_back(node->getStringValue());
}
for (int i=0; i<node->nChildren(); ++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<Highlight>();
for (int menu_p=0; menu_p<menubar->nChildren(); ++menu_p) {
SGPropertyNode* menu = menubar->getChild(menu_p);
if (menu->getNameString() != "menu") continue;
for (int item_p=0; item_p<menu->nChildren(); ++item_p) {
SGPropertyNode* item = menu->getChild(item_p);
if (item->getNameString() != "item") continue;
std::vector<std::string> 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<Highlight>();
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<std::string> 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());
@ -405,21 +477,16 @@ NewGUI::readDir (const SGPath& path)
}
// we need to parse the actual XML
SGPropertyNode_ptr props = new SGPropertyNode;
try {
readProperties(xmlPath, props);
} catch (const sg_exception &) {
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()) {
@ -429,7 +496,7 @@ NewGUI::readDir (const SGPath& path)
} // of directory children iteration
txn.commit();
}
}
////////////////////////////////////////////////////////////////////////
// Style handling.
////////////////////////////////////////////////////////////////////////

View file

@ -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;

View file

@ -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 <readonly> 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;
};

View file

@ -100,6 +100,7 @@
#include <Canvas/gui_mgr.hxx>
#include <Canvas/FGCanvasSystemAdapter.hxx>
#include <GUI/new_gui.hxx>
#include <GUI/Highlight.hxx>
#include <GUI/MessageBox.hxx>
#include <Input/input.hxx>
#include <Instrumentation/instrument_mgr.hxx>
@ -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<Highlight>(SGSubsystemMgr::INIT);
globals->add_subsystem("gui", new NewGUI, SGSubsystemMgr::INIT);
}

View file

@ -15,6 +15,7 @@
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/model/animation.hxx>
#include <simgear/scene/model/placement.hxx>
#include <simgear/scene/util/SGNodeMasks.hxx>
#include <simgear/scene/model/modellib.hxx>
@ -26,6 +27,7 @@
#include <Viewer/view.hxx>
#include <Scenery/scenery.hxx>
#include <Sound/fg_fx.hxx>
#include <GUI/Highlight.hxx>
#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<Highlight>();
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 <group> are animated by properties in
// <m_highlight_names>, so register the association between <group> 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<SGExpressiond const> expression = TransformExpression(&node);
int num_added = 0;
if (expression) {
// All <nodes>'s child nodes will be affected by <expression> so
// add all of <expression>'s properties to <m_highlight_names> so
// that we can call Highlight::add_property_node() for each child
// node.
std::set<const SGPropertyNode*> 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 <m_highlight_names> 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<std::string> 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

View file

@ -12,6 +12,7 @@
#include <memory>
#include <simgear/structure/subsystem_mgr.hxx> // for SGSubsystem
#include <simgear/math/SGVec3.hxx>
// Don't pull in the headers, since we don't need them here.

View file

@ -94,6 +94,7 @@
#include <Scenery/redout.hxx>
#include <GUI/new_gui.hxx>
#include <GUI/gui.h>
#include <GUI/Highlight.hxx>
#include <Instrumentation/HUD/HUD.hxx>
#include <Environment/precipitation_mgr.hxx>
@ -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<Highlight>();
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;