11#include <simgear/misc/strutils.hxx>
12#include <simgear/props/props.hxx>
13#include <simgear/props/props_io.hxx>
22using namespace std::string_literals;
23namespace strutils = simgear::strutils;
34 auto labelNode = _config->getChild(
"label");
36 labelNode->removeChangeListener(
this);
40 _value->removeChangeListener(
this);
46 return ctx.to_nasal(SGSharedPtr<PUICompatObject>(
47 new PUICompatObject(ctx.requireArg<naRef>(0), ctx.requireArg<std::string>(1))));
52 const auto key = ctx.requireArg<std::string>(0);
53 const auto resource = ctx.getArg<std::string>(1);
54 const auto domain = ctx.getArg<std::string>(2);
60 const auto key = ctx.requireArg<std::string>(0);
61 const auto cardinal = ctx.requireArg<
int>(1);
62 const auto resource = ctx.getArg<std::string>(2);
63 const auto domain = ctx.getArg<std::string>(3);
70 using NasalGUIObject = nasal::Ghost<SGSharedPtr<PUICompatObject>>;
71 NasalGUIObject::init(
"gui.xml.CompatObject")
72 .bases<nasal::ObjectRef>()
74 .method(
"configValue", &PUICompatObject::nasalGetConfigValue)
94 .method(
"gridLocation", &PUICompatObject::gridLocation)
98 nasal::Hash objectHash = compatModule.createHash(
"Object");
107 nasal::Hash guiModule{nas->getModule(
"gui"), ctx};
108 if (guiModule.isNil()) {
109 throw sg_exception(
"Can't initialize PUICompat Nasal");
112 auto f = guiModule.get<std::function<
PUICompatObjectRef(std::string)>>(
"_createCompatObject");
121 const auto uiVersion =
dialog()->uiVersion();
123 _name = _config->getStringValue(
"name");
124 _label = _config->getStringValue(
"label");
125 bool isLive = _config->getBoolValue(
"live");
127 int parentWidth = 800;
128 int parentHeight = 600;
131 int width = _config->getIntValue(
"width", parentWidth);
132 int height = _config->getIntValue(
"height", parentHeight);
133 int x = _config->getIntValue(
"x", (parentWidth -
width) / 2);
134 int y = _config->getIntValue(
"y", (parentHeight -
height) / 2);
136 setGeometry(SGRectd{
static_cast<double>(x),
static_cast<double>(y),
137 static_cast<double>(
width),
static_cast<double>(
height)});
139 if (_config->hasChild(
"visible")) {
140 _visibleCondition = sgReadCondition(
globals->get_props(), _config->getChild(
"visible"));
143 if (_config->hasChild(
"enable")) {
144 _enableCondition = sgReadCondition(
globals->get_props(), _config->getChild(
"enable"));
147 if (_config->hasChild(
"label")) {
148 _config->getChild(
"label")->addChangeListener(
this);
151 if (_config->hasValue(
"property")) {
152 _value =
fgGetNode(_config->getStringValue(
"property"),
true);
155 _live = LiveValueMode::Listener;
156 if (_value->isTied() || _value->isAlias()) {
157 const auto isSafe = _value->getAttribute(SGPropertyNode::LISTENER_SAFE);
159 SG_LOG(SG_GUI, SG_DEV_WARN,
"Requested live updating of unsafe tied property: " << _value->getPath() <<
"; please fix this propertty to be non-tied or make it listener-safe explicitly.");
162 _live = LiveValueMode::Polled;
166 if (_live == LiveValueMode::Listener) {
167 _value->addChangeListener(
this);
173 if (uiVersion >= 2) {
174 if (_type ==
"radio") {
175 auto g = _config->getStringValue(
"radio-group");
177 SG_LOG(SG_GUI, SG_DEV_WARN,
"UIv2 radio button does not specify a group ID (at " << _config->getLocation() <<
")");
182 const auto bindings = _config->getChildren(
"binding");
183 if (!bindings.empty()) {
184 for (
auto bindingNode : bindings) {
185 const auto cmd = bindingNode->getStringValue(
"command");
186 if (cmd ==
"nasal") {
195 SGPropertyNode_ptr copiedBinding =
new SGPropertyNode;
196 copyProperties(bindingNode, copiedBinding);
197 copiedBinding->setStringValue(
"module",
dialog()->nasalModule());
199 bindingNode = copiedBinding;
202 _bindings.push_back(
new SGBinding(bindingNode,
globals->get_props()));
207 int nChildren = _config->nChildren();
208 for (
int i = 0;
i < nChildren;
i++) {
209 auto childNode = _config->getChild(
i);
211 const auto nodeName = childNode->getNameString();
216 SGSharedPtr<PUICompatObject> childObject =
createForType(nodeName, childNode);
217 childObject->_parent =
this;
218 _children.push_back(childObject);
222 callMethod<void>(
"init", nas->wrappedPropsNode(_config));
225 for (
auto c : _children) {
229 callMethod<void>(
"postinit");
234 const auto uiVersion =
dialog()->uiVersion();
236 throw std::runtime_error(
"radioGroupIdent: Not allowed at UI version < 2");
239 return _config->getStringValue(
"radio-group");
245 return callMethod<naRef>(
"show", viewParent);
251 "button",
"one-shot",
"slider",
"dial",
252 "text",
"input",
"radio",
253 "combo",
"textbox",
"select",
254 "hrule",
"vrule",
"group",
"frame",
257 if (uiVersion >= 2) {
258 typeNames.push_back(
"standard-button");
259 typeNames.push_back(
"tabs");
260 typeNames.push_back(
"button-box");
263 auto it = std::find(typeNames.begin(), typeNames.end(), nm);
264 return it != typeNames.end();
269 if (_enableCondition) {
270 const bool e = _enableCondition->test();
273 callMethod<void, bool>(
"enabledChanged", e);
277 if (_visibleCondition) {
278 const bool e = _visibleCondition->test();
281 callMethod<void, bool>(
"visibleChanged", e);
286 _labelChanged =
false;
287 callMethod<void, std::string>(
"labelChanged", _label);
291 if (_live == LiveValueMode::Polled) {
294 const auto nv = _value->getStringValue();
295 if (nv != _oldPolledValue) {
296 _valueChanged =
true;
297 _oldPolledValue = nv;
302 _valueChanged =
false;
303 callMethod<void>(
"valueChanged");
314 if (_live != LiveValueMode::OnApply) {
319 const auto nv = _value->getStringValue();
320 if (nv != _oldPolledValue) {
321 _valueChanged =
true;
322 _oldPolledValue = nv;
330 callMethod<void>(
"apply");
331 if (_live == LiveValueMode::OnApply) {
332 _valueChanged =
false;
357naRef PUICompatObject::nasalGetConfigValue(
const nasal::CallContext ctx)
const
359 auto name = ctx.requireArg<std::string>(0);
360 naRef defaultVal = ctx.getArg(1, naNil());
361 SGPropertyNode_ptr nd = _config->getChild(
name);
362 if (!nd || !nd->hasValue())
370 if (node->getNameString() ==
"label") {
371 _labelChanged =
true;
372 _label = node->getStringValue();
376 if (_live == LiveValueMode::OnApply)
380 _valueChanged =
true;
386 SG_LOG(SG_GUI, SG_DEV_ALERT,
"Skipping binding activation for disabled widget:" <<
name());
393 guiSub->setActiveDialog(
dialog());
394 fireBindingList(_bindings);
395 guiSub->setActiveDialog(
nullptr);
400 return !_bindings.empty();
410 if (newGeom == _geometry) {
415 callMethod<void>(
"geometryChanged");
422 return _geometry.pos().x();
427 return _geometry.pos().y();
432 return _geometry.width();
437 return _geometry.height();
447 return _parent.lock();
457 if (_visibleCondition) {
458 return _visibleCondition->test();
466 if (_enableCondition) {
467 return _enableCondition->test();
480 if (_visibleCondition) {
481 SG_LOG(SG_GUI, SG_DEV_ALERT,
"Trying to set visiblity on widget with visible condition already defined");
489 callMethod<void, bool>(
"visibleChanged", _visible);
494 if (_enableCondition) {
495 SG_LOG(SG_GUI, SG_DEV_ALERT,
"Trying to set enabled on widget with enable condition already defined");
503 callMethod<void, bool>(
"enabledChanged", _enabled);
512 for (
auto child : _children) {
513 auto r = child->widgetByName(
name);
522void PUICompatObject::recursiveUpdate(
const std::string& objectName)
524 if (objectName.empty() || (objectName == _name)) {
528 for (
auto child : _children) {
529 child->recursiveUpdate(objectName);
533void PUICompatObject::recursiveUpdateValues(
const std::string& objectName)
535 if (objectName.empty() || (objectName == _name)) {
539 for (
auto child : _children) {
540 child->recursiveUpdateValues(objectName);
545void PUICompatObject::recursiveApply(
const std::string& objectName)
547 if (objectName.empty() || (objectName == _name)) {
551 for (
auto child : _children) {
552 child->recursiveApply(objectName);
556void PUICompatObject::recursiveOnDelete()
559 for (
auto child : _children) {
560 child->recursiveOnDelete();
563 callMethod<void>(
"del");
578 return _dialog.lock();
581nasal::Hash PUICompatObject::gridLocation(
const nasal::CallContext& ctx)
const
583 nasal::Hash result{ctx.c_ctx()};
584 result.set(
"column", _config->getIntValue(
"col"));
585 result.set(
"row", _config->getIntValue(
"row"));
586 result.set(
"columnSpan", _config->getIntValue(
"colspan", 1));
587 result.set(
"rowSpan", _config->getIntValue(
"rowspan", 1));
593 auto strippedKey = strutils::strip(key);
594 auto res = resource.empty() ?
"dialog-"s +
dialog()->getName() : resource;
595 auto dom = domain.empty() ?
dialog()->translationDomain() : domain;
602 auto strippedKey = strutils::strip(key);
603 auto res = resource.empty() ?
"dialog-"s +
dialog()->getName() : resource;
604 auto dom = domain.empty() ?
dialog()->translationDomain() : domain;
Class for retrieving translated strings.
naRef f_translateString(const PUICompatObject &widget, nasal::CallContext ctx)
naRef propNodeGhostCreate(naContext c, SGPropertyNode *n)
naRef f_makeCompatObjectPeer(const nasal::CallContext &ctx)
naRef f_translatePluralString(const PUICompatObject &widget, nasal::CallContext ctx)
SGSharedPtr< FGPUICompatDialog > PUICompatDialogRef
SGSharedPtr< PUICompatObject > PUICompatObjectRef
std::vector< PUICompatObjectRef > PUICompatObjectVec
static naRef getPropertyValue(naContext c, SGPropertyNode *node)
Convert the value of an SGPropertyNode to its Nasal representation.
naRef wrappedPropsNode(SGPropertyNode *aProps)
create Nasal props.Node for an SGPropertyNode* This is the actual ghost, wrapped in a Nasal sugar cla...
Class for retrieving translated strings.
std::string getPluralWithDefault(intType cardinalNumber, const std::string &resource, const std::string &basicId, const std::string &defaultValue, int index=0) const
Same as getWithDefault(), but for a string that has plural forms.
std::string getWithDefault(const std::string &resource, const std::string &basicId, const std::string &defaultValue, int index=0) const
Get a single translation, with default for missing or empty strings.
XML-configured GUI subsystem.
std::string radioGroupIdent() const
return the radio group ID associated with this widget (which is presumably a radio-button)
std::string translatePluralString(const std::string &key, int cardinal, const std::string &resource={}, const std::string &domain={}) const
void valueChanged(SGPropertyNode *node) override
virtual void updateGeometry(const SGRectd &newGeom)
std::string translateString(const std::string &key, const std::string &resource={}, const std::string &domain={}) const
void setGeometry(const SGRectd &g)
PUICompatObject(naRef impl, const std::string &type)
const std::string & name() const
naRef show(naRef viewParent)
virtual void activateBindings()
friend naRef f_makeCompatObjectPeer(const nasal::CallContext &ctx)
PUICompatObjectRef parent() const
PUICompatDialogRef dialog() const
PUICompatObjectRef widgetByName(const std::string &name) const
find an object (which might be us, or a descendant) with the corresponding name, or nullptr.
virtual ~PUICompatObject()
static void setupGhost(nasal::Hash &guiModule)
static PUICompatObjectRef createForType(const std::string &type, SGPropertyNode_ptr config)
naRef propertyValue(naContext ctx) const
return the actual Nasal value of our property: this avoids the need to create a the property ghost an...
const std::string & type() const
naRef property() const
return the wrapped props,Node corresponding to our property
PUICompatObjectVec children() const
virtual void updateValue()
static bool isNodeAChildObject(const std::string &nm, int uiVersion)
std::vector< std::string > string_list
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.