diff --git a/simgear/scene/model/SGReaderWriterXML.cxx b/simgear/scene/model/SGReaderWriterXML.cxx index f04c84b3..736c3056 100644 --- a/simgear/scene/model/SGReaderWriterXML.cxx +++ b/simgear/scene/model/SGReaderWriterXML.cxx @@ -256,6 +256,208 @@ namespace { } } +struct DumpSGPropertyNode { + + SGPropertyNode* _node; + const std::string& _indent; + + DumpSGPropertyNode(SGPropertyNode* node, const std::string& indent="") + : + _node(node), + _indent(indent) + {} + + friend std::ostream& operator << (std::ostream& out, const DumpSGPropertyNode& dump) + { + if (!dump._node) return out; + out << dump._indent << dump._node->getDisplayName() << "=" << dump._node->getStringValue() << "\n"; + for (int i=0; inChildren(); ++i) { + SGPropertyNode* child = dump._node->getChild(i); + out << DumpSGPropertyNode(child, dump._indent + " "); + } + return out; + } +}; + +struct DumpOsgNode { + + osg::Node* _node; + const std::string& _indent; + + DumpOsgNode(osg::Node* node, const std::string& indent="") + : + _node(node), + _indent(indent) + {} + + friend std::ostream& operator << (std::ostream& out, const DumpOsgNode& dump) + { + if (!dump._node) return out; + out << dump._indent << dump._node->getName() << "\n"; + osg::Group* group = dynamic_cast(dump._node); + if (group) { + for (unsigned i=0; igetNumChildren(); ++i) { + out << DumpOsgNode(group->getChild(i), dump._indent + " "); + } + } + return out; + } +}; + +/* Finds all names recursively in an osg::Node. */ +struct OSGNodeGetNames +{ + std::vector names; + + OSGNodeGetNames(osg::Node* node=NULL) + { + add(node); + } + + void add(osg::Node* node) + { + if (!node) return; + const std::string& name = node->getName(); + if (name != "") names.push_back(name); + + osg::Group* group = dynamic_cast(node); + if (group) { + for (unsigned i=0; igetNumChildren(); ++i) { + add(group->getChild(i)); + } + } + } +}; + + +/* For each existing animation in , we add a tooltip showing information +on the properties that the animation depends on. + +At runtime, the tooltips only show if sim/animation-tooltips is true. + +The dummy animations will show up as yellow (like clickable items) if the user +presses Ctrl-C, even if tooltips aren't showing because sim/animation-tooltips +is false. */ +void addTooltipAnimations(const SGPath& path, SGPropertyNode_ptr props, osg::ref_ptr model, int autoTooltipsMasterMax) +{ + OSGNodeGetNames model_names; + if (0) { + /* Include all names in the .asi file, in an attempt to make + tooltips activate for more than just the needle in instrument dials + for example. This doesn't seem to help. */ + model_names.add(model); + if (!model_names.names.empty()) { + /* First name will be filename of .ac file. */ + model_names.names.erase(model_names.names.begin()); + } + } + + /* For each animation add an extra animation with type=pick containing + set-tooltip. We use the object-name as the tooltip-id, and we use the + animation's property name/value(s) in the tooltip label. */ + PropertyList animations = props->getChildren("animation"); + SG_LOG(SG_INPUT, SG_DEBUG, "animations.size()=" << animations.size() + << " path=" << path + << " props=" << (props ? props->getPath() : "") + ); + for (unsigned i = 0; i < animations.size(); ++i) { + + SGPropertyNode* animation = animations[i]; + + if (!strcmp(animation->getStringValue("type"), "pick")) { + /* There appear to be many of these, and we end up consuming + GB's of memory if we install a tooltip for each one, so ignore. + */ + continue; + } + + PropertyList properties = animation->getChildren("property"); + if (properties.empty()) { + /* We can't really show anything useful in tooltip. */ + continue; + } + + PropertyList objectnames(animation->getChildren("object-name")); + if (objectnames.empty() && model_names.names.empty()) { + continue; + } + + /* Make a unique tooltip-id. */ + std::string id = "auto-tooltip-"; + { + static int id_n = 0; + id_n += 1; + if (autoTooltipsMasterMax > 0 && id_n > autoTooltipsMasterMax) { + continue; + } + std::ostringstream s; + s << id_n; + id += s.str(); + } + + /* If we reach here, we create a new dummy animation with type="pick" + that will implement a tooltip with information about . */ + SGPropertyNode* new_animation = props->addChild("animation"); + + new_animation->addChild("type")->setValue("pick"); + + /* Use 's object-names so that our tooltip appears whenever + the user hovers over 's objects. */ + for (unsigned i=0; igetStringValue(); + new_animation->addChild("object-name")->setStringValue(objectname); + } + + for (auto name: model_names.names) { + bool found = false; + for (auto objectname: objectnames) { + if (name == objectname->getStringValue()) { + found = true; + break; + } + } + if (!found) { + new_animation->addChild("object-name")->setStringValue(name); + } + } + + SGPropertyNode* hovered_binding = new_animation->addChild("hovered")->addChild("binding"); + hovered_binding->addChild("command")->setValue("set-tooltip"); + hovered_binding->addChild("condition")->addChild("property")->setValue("sim/animation-tooltips"); + + hovered_binding->addChild("tooltip-id")->setStringValue(id); + + /* Build up printf-style label string showing the property values + that depend on. */ + std::string label; + for (unsigned i=0; i 0) label += " "; + SGPropertyNode* p = properties[i]; + /* Using an alias here (rather then just copying the path) ensures + things work if

is itself an alias. */ + hovered_binding->addChild("property")->alias(p); + if (p->isAlias()) { + /* We only get things like "/rpm[0]" here, rather than the full + path of the property to which p points. */ + label += p->getAliasTarget()->getPath(); + } + else { + label += p->getStringValue(); + } + label += "=%s"; + } + hovered_binding->addChild("label")->setStringValue(label); + + SG_LOG(SG_INPUT, SG_BULK, "have added new_animation:\n" + << DumpSGPropertyNode(new_animation, " ") + ); + } + + SG_LOG(SG_INPUT, SG_BULK, "props for path=" << path << ":\n" + << DumpSGPropertyNode(props, " ") + ); +} + static std::tuple sgLoad3DModel_internal(const SGPath& path, const osgDB::Options* dbOptions, @@ -295,6 +497,10 @@ sgLoad3DModel_internal(const SGPath& path, << t.getFormattedMessage()); throw; } + if (options->getAutoTooltipsMaster()) { + addTooltipAnimations(path, props, model, options->getAutoTooltipsMasterMax()); + } + if (overlay) copyProperties(overlay, props); diff --git a/simgear/scene/model/modellib.cxx b/simgear/scene/model/modellib.cxx index fe8f84d3..ad990b6e 100644 --- a/simgear/scene/model/modellib.cxx +++ b/simgear/scene/model/modellib.cxx @@ -128,7 +128,9 @@ osg::Node* SGModelLib::loadModel(const string &path, SGPropertyNode *prop_root, SGModelData *data, - bool load2DPanels) + bool load2DPanels, + bool autoTooltipsMaster, + int autoTooltipsMasterMax) { osg::ref_ptr opt; opt = SGReaderWriterOptions::copyOrCreate(osgDB::Registry::instance()->getOptions()); @@ -140,6 +142,9 @@ SGModelLib::loadModel(const string &path, opt->setLoadPanel(static_panelFunc); } + opt->setAutoTooltipsMaster(autoTooltipsMaster); + opt->setAutoTooltipsMasterMax(autoTooltipsMasterMax); + osg::Node *n = loadFile(path, opt.get()); if (n && n->getName().empty()) n->setName("Direct loaded model \"" + path + "\""); diff --git a/simgear/scene/model/modellib.hxx b/simgear/scene/model/modellib.hxx index 6032bb1e..65968c74 100644 --- a/simgear/scene/model/modellib.hxx +++ b/simgear/scene/model/modellib.hxx @@ -59,7 +59,9 @@ public: // data->modelLoaded() will be called after the model is loaded static osg::Node* loadModel(const std::string &path, SGPropertyNode *prop_root = NULL, - SGModelData *data=0, bool load2DPanels=false); + SGModelData *data=0, bool load2DPanels=false, + bool autoTooltipsMaster=false, + int autoTooltipsMasterMax=0); // Load a 3D model (any format) through the DatabasePager. // This function initially just returns a proxy node that refers to diff --git a/simgear/scene/util/SGReaderWriterOptions.hxx b/simgear/scene/util/SGReaderWriterOptions.hxx index e22648f0..fa0ffc98 100644 --- a/simgear/scene/util/SGReaderWriterOptions.hxx +++ b/simgear/scene/util/SGReaderWriterOptions.hxx @@ -55,6 +55,8 @@ public: _load_panel(0), _instantiateEffects(false), _instantiateMaterialEffects(false), + _autoTooltipsMaster(false), + _autoTooltipsMasterMax(0), _LoadOriginHint(ORIGIN_MODEL) { } SGReaderWriterOptions(const std::string& str) : @@ -63,6 +65,8 @@ public: _load_panel(0), _instantiateEffects(false), _instantiateMaterialEffects(false), + _autoTooltipsMaster(false), + _autoTooltipsMasterMax(0), _LoadOriginHint(ORIGIN_MODEL) { } SGReaderWriterOptions(const osgDB::Options& options, @@ -72,6 +76,8 @@ public: _load_panel(0), _instantiateEffects(false), _instantiateMaterialEffects(false), + _autoTooltipsMaster(false), + _autoTooltipsMasterMax(0), _LoadOriginHint(ORIGIN_MODEL) { } SGReaderWriterOptions(const SGReaderWriterOptions& options, @@ -88,6 +94,8 @@ public: _instantiateMaterialEffects(options._instantiateMaterialEffects), _materialName(options._materialName), _sceneryPathSuffixes(options._sceneryPathSuffixes), + _autoTooltipsMaster(options._autoTooltipsMaster), + _autoTooltipsMasterMax(options._autoTooltipsMasterMax), _LoadOriginHint(ORIGIN_MODEL) { } @@ -142,6 +150,16 @@ public: void setSceneryPathSuffixes(const string_list& suffixes) { _sceneryPathSuffixes = suffixes; } + + bool getAutoTooltipsMaster() const + { return _autoTooltipsMaster; } + void setAutoTooltipsMaster(bool autoTooltipsMaster) + { _autoTooltipsMaster = autoTooltipsMaster; } + + int getAutoTooltipsMasterMax() const + { return _autoTooltipsMasterMax; } + void setAutoTooltipsMasterMax(int autoTooltipsMasterMax) + { _autoTooltipsMasterMax = autoTooltipsMasterMax; } static SGReaderWriterOptions* copyOrCreate(const osgDB::Options* options); static SGReaderWriterOptions* fromPath(const SGPath& path); @@ -176,6 +194,8 @@ private: bool _instantiateMaterialEffects; string _materialName; string_list _sceneryPathSuffixes; + bool _autoTooltipsMaster; + int _autoTooltipsMasterMax; SGGeod _geod; mutable LoadOriginHint _LoadOriginHint; };