beremiz

Python Safe Globals now have more reliable triggering of OnChange call. Added "Onchange" object to accessible runtime variables that let user python code see count of changes and first and last values.
// widgets_common.ysl2
in xsl decl labels(*ptr, name="defs_by_labels") alias call-template {
with "hmi_element", "$hmi_element";
with "labels"{text *ptr};
content;
};
decl optional_labels(*ptr) alias - {
/* TODO add some per label xslt variable to check if exist */
labels(*ptr){
with "mandatory","'no'";
content;
}
};
decl activable_labels(*ptr) alias - {
optional_labels(*ptr) {
with "subelements","'active inactive'";
content;
}
};
template "svg:*", mode="hmi_widgets" {
const "widget", "func:widget(@id)";
const "eltid","@id";
const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,`
const "indexes" foreach "$widget/path" {
choose {
when "not(@index)" {
choose {
when "not(@type)" {
warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree
> undefined`if "position()!=last()" > ,`
}
when "@type = 'PAGE_LOCAL'"
> "«@value»"`if "position()!=last()" > ,`
when "@type = 'HMI_LOCAL'"
> hmi_local_index("«@value»")`if "position()!=last()" > ,`
}
}
otherwise {
> «@index»`if "position()!=last()" > ,`
}
}
}
| "«@id»": new «$widget/@type»Widget ("«@id»",[«$args»],[«$indexes»],{
apply "$widget", mode="widget_defs" with "hmi_element",".";
| })`if "position()!=last()" > ,`
}
def "func:unique_types" {
param "elts_with_type";
choose {
when "count($elts_with_type) > 1" {
const "prior_results","func:unique_types($elts_with_type[position()!=last()])";
choose {
when "$elts_with_type[last()][@type = $prior_results/@type]"{
// type already in
result "$prior_results";
}
otherwise {
result "$prior_results | $elts_with_type[last()]";
}
}
}
otherwise {
result "$elts_with_type";
}
}
}
emit "preamble:local-variable-indexes" {
||
let hmi_locals = {};
var last_remote_index = hmitree_types.length - 1;
var next_available_index = hmitree_types.length;
const local_defaults = {
||
foreach "$parsed_widgets/widget[@type = 'VarInit']"{
if "count(path) != 1" error > VarInit «@id» must have only one variable given.
if "path/@type != 'PAGE_LOCAL' and path/@type != 'HMI_LOCAL'" error > VarInit «@id» only applies to HMI variable.
| "«path/@value»":«arg[1]/@value»`if "position()!=last()" > ,`
}
||
};
var cache = hmitree_types.map(_ignored => undefined);
function page_local_index(varname, pagename){
let pagevars = hmi_locals[pagename];
let new_index;
if(pagevars == undefined){
new_index = next_available_index++;
hmi_locals[pagename] = {[varname]:new_index}
} else {
let result = pagevars[varname];
if(result != undefined) {
return result;
}
new_index = next_available_index++;
pagevars[varname] = new_index;
}
let defaultval = local_defaults[varname];
if(defaultval != undefined)
cache[new_index] = defaultval;
return new_index;
}
function hmi_local_index(varname){
return page_local_index(varname, "HMI_LOCAL");
}
||
}
emit "preamble:widget-base-class" {
||
var pending_widget_animates = [];
class Widget {
offset = 0;
frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */
unsubscribable = false;
pending_animate = false;
constructor(elt_id,args,indexes,members){
this.element_id = elt_id;
this.element = id(elt_id);
this.args = args;
this.indexes = indexes;
Object.keys(members).forEach(prop => this[prop]=members[prop]);
}
unsub(){
/* remove subsribers */
if(!this.unsubscribable)
for(let i = 0; i < this.indexes.length; i++) {
let index = this.indexes[i];
if(this.relativeness[i])
index += this.offset;
subscribers(index).delete(this);
}
this.offset = 0;
this.relativeness = undefined;
}
sub(new_offset=0, relativeness, container_id){
this.offset = new_offset;
this.relativeness = relativeness;
this.container_id = container_id ;
/* add this's subsribers */
if(!this.unsubscribable)
for(let i = 0; i < this.indexes.length; i++) {
let index = this.get_variable_index(i);
if(index == undefined) continue;
subscribers(index).add(this);
}
need_cache_apply.push(this);
}
apply_cache() {
if(!this.unsubscribable) for(let index in this.indexes){
/* dispatch current cache in newly opened page widgets */
let realindex = this.get_variable_index(index);
if(realindex == undefined) continue;
let cached_val = cache[realindex];
if(cached_val != undefined)
this._dispatch(cached_val, cached_val, index);
}
}
get_variable_index(varnum) {
let index = this.indexes[varnum];
if(typeof(index) == "string"){
index = page_local_index(index, this.container_id);
} else {
if(this.relativeness[varnum]){
index += this.offset;
}
}
return index;
}
change_hmi_value(index, opstr) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
return change_hmi_value(realindex, opstr);
}
apply_hmi_value(index, new_val) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
return apply_hmi_value(realindex, new_val);
}
new_hmi_value(index, value, oldval) {
// TODO avoid searching, store index at sub()
for(let i = 0; i < this.indexes.length; i++) {
let refindex = this.get_variable_index(i);
if(refindex == undefined) continue;
if(index == refindex) {
this._dispatch(value, oldval, i);
break;
}
}
}
_dispatch(value, oldval, varnum) {
let dispatch = this.dispatch;
if(dispatch != undefined){
try {
dispatch.call(this, value, oldval, varnum);
} catch(err) {
console.log(err);
}
}
}
_animate(){
this.animate();
this.pending_animate = false;
}
request_animate(){
if(!this.pending_animate){
pending_widget_animates.push(this);
this.pending_animate = true;
requestHMIAnimation();
}
}
}
||
}
emit "declarations:hmi-classes" {
const "used_widget_types", "func:unique_types($parsed_widgets/widget)";
apply "$used_widget_types", mode="widget_class";
}
template "widget", mode="widget_class"
||
class «@type»Widget extends Widget{
/* empty class, as «@type» widget didn't provide any */
}
||
const "excluded_types", "str:split('Page Lang VarInit')";
const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types)]/@id";
emit "declarations:hmi-elements" {
| var hmi_widgets = {
apply "$hmi_elements[@id = $included_ids]", mode="hmi_widgets";
| }
}
function "defs_by_labels" {
param "labels","''";
param "mandatory","'yes'";
param "subelements","/..";
param "hmi_element";
const "widget_type","@type";
foreach "str:split($labels)" {
const "name",".";
const "elt","$result_svg_ns//*[@id = $hmi_element/@id]//*[@inkscape:label=$name][1]";
choose {
when "not($elt/@id)" {
if "$mandatory='yes'" {
error > «$widget_type» widget must have a «$name» element
}
// otherwise produce nothing
}
otherwise {
| «$name»_elt: id("«$elt/@id»"),
if "$subelements" {
| «$name»_sub: {
foreach "str:split($subelements)" {
const "subname",".";
const "subelt","$elt/*[@inkscape:label=$subname][1]";
choose {
when "not($subelt/@id)" {
if "$mandatory='yes'" {
error > «$widget_type» widget must have a «$name»/«$subname» element
}
| /* missing «$name»/«$subname» element */
}
otherwise {
| "«$subname»": id("«$subelt/@id»")`if "position()!=last()" > ,`
}
}
}
| },
}
}
}
}
}
def "func:escape_quotes" {
param "txt";
// have to use a python string to enter escaped quote
// const "frstln", "string-length($frst)";
choose {
when !"contains($txt,'\"')"! {
result !"concat(substring-before($txt,'\"'),'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!;
}
otherwise {
result "$txt";
}
}
}