// 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
}
when "@type = 'PAGE_LOCAL'"
> "«@value»"
when "@type = 'HMI_LOCAL'"
> hmi_local_index("«@value»")
otherwise
error > Internal error while processing widget's non indexed HMI tree path : unknown type
}
}
otherwise {
> «@index»
}
}
if "position()!=last()" > ,
}
const "minmaxes" foreach "$widget/path" {
choose {
when "@min and @max"
> [«@min»,«@max»]
otherwise
> undefined
}
if "position()!=last()" > ,
}
| "«@id»": new «$widget/@type»Widget ("«@id»",[«$args»],[«$indexes»],[«$minmaxes»],{
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,minmaxes,members){
this.element_id = elt_id;
this.element = id(elt_id);
this.args = args;
this.indexes = indexes;
this.minmaxes = minmaxes;
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;
}
overshot(new_val, max) {
}
undershot(new_val, min) {
}
clip_min_max(index, new_val) {
let minmax = this.minmaxes[index];
if(minmax !== undefined && typeof new_val == "number") {
let [min,max] = minmax;
if(new_val < min){
this.undershot(new_val, min);
return min;
}
if(new_val > max){
this.overshot(new_val, max);
return max;
}
}
return new_val;
}
change_hmi_value(index, opstr) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
let old_val = cache[realindex];
let new_val = eval_operation_string(old_val, opstr);
new_val = this.clip_min_max(index, new_val);
return apply_hmi_value(realindex, new_val);
}
apply_hmi_value(index, new_val) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
new_val = this.clip_min_max(index, new_val);
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";
}
}
}