|
1 // widgets_common.ysl2 |
|
2 |
|
3 in xsl decl labels(*ptr, name="defs_by_labels") alias call-template { |
|
4 with "hmi_element", "$hmi_element"; |
|
5 with "labels"{text *ptr}; |
|
6 content; |
|
7 }; |
|
8 |
|
9 decl optional_labels(*ptr) alias - { |
|
10 /* TODO add some per label xslt variable to check if exist */ |
|
11 labels(*ptr){ |
|
12 with "mandatory","'no'"; |
|
13 content; |
|
14 } |
|
15 }; |
|
16 |
|
17 decl activable_labels(*ptr) alias - { |
|
18 optional_labels(*ptr) { |
|
19 with "subelements","'active inactive'"; |
|
20 content; |
|
21 } |
|
22 }; |
|
23 |
|
24 in xsl decl widget_desc(%name, match="widget[@type='%name']", mode="widget_desc") alias template { |
|
25 type > «@type» |
|
26 content; |
|
27 }; |
|
28 |
|
29 in xsl decl widget_class(%name, *clsname="%nameWidget", match="widget[@type='%name']", mode="widget_class") alias template { |
|
30 | class `text **clsname` extends Widget{ |
|
31 content; |
|
32 | } |
|
33 }; |
|
34 |
|
35 in xsl decl widget_defs(%name, match="widget[@type='%name']", mode="widget_defs") alias template { |
|
36 param "hmi_element"; |
|
37 content; |
|
38 }; |
|
39 |
|
40 in xsl decl widget_page(%name, match="widget[@type='%name']", mode="widget_page") alias template { |
|
41 param "page_desc"; |
|
42 content; |
|
43 }; |
|
44 |
|
45 decl gen_index_xhtml alias - { |
|
46 content; |
|
47 }; |
|
48 |
|
49 template "svg:*", mode="hmi_widgets" { |
|
50 const "widget", "func:widget(@id)"; |
|
51 const "eltid","@id"; |
|
52 const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,` |
|
53 const "indexes" foreach "$widget/path" { |
|
54 choose { |
|
55 when "not(@index)" { |
|
56 choose { |
|
57 when "not(@type)" { |
|
58 warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree |
|
59 > undefined |
|
60 } |
|
61 when "@type = 'PAGE_LOCAL'" |
|
62 > "«@value»" |
|
63 when "@type = 'HMI_LOCAL'" |
|
64 > hmi_local_index("«@value»") |
|
65 otherwise |
|
66 error > Internal error while processing widget's non indexed HMI tree path : unknown type |
|
67 } |
|
68 } |
|
69 otherwise { |
|
70 > «@index» |
|
71 } |
|
72 } |
|
73 if "position()!=last()" > , |
|
74 } |
|
75 |
|
76 const "minmaxes" foreach "$widget/path" { |
|
77 choose { |
|
78 when "@min and @max" |
|
79 > [«@min»,«@max»] |
|
80 otherwise |
|
81 > undefined |
|
82 } |
|
83 if "position()!=last()" > , |
|
84 } |
|
85 |
|
86 | "«@id»": new «$widget/@type»Widget ("«@id»",[«$args»],[«$indexes»],[«$minmaxes»],{ |
|
87 apply "$widget", mode="widget_defs" with "hmi_element","."; |
|
88 | })`if "position()!=last()" > ,` |
|
89 } |
|
90 |
|
91 emit "preamble:local-variable-indexes" { |
|
92 || |
|
93 |
|
94 let hmi_locals = {}; |
|
95 var last_remote_index = hmitree_types.length - 1; |
|
96 var next_available_index = hmitree_types.length; |
|
97 let cookies = new Map(document.cookie.split("; ").map(s=>s.split("="))); |
|
98 |
|
99 const local_defaults = { |
|
100 || |
|
101 foreach "$parsed_widgets/widget[starts-with(@type,'VarInit')]"{ |
|
102 if "count(path) != 1" error > VarInit «@id» must have only one variable given. |
|
103 if "path/@type != 'PAGE_LOCAL' and path/@type != 'HMI_LOCAL'" error > VarInit «@id» only applies to HMI variable. |
|
104 > "«path/@value»": |
|
105 choose { |
|
106 when "@type = 'VarInitPersistent'" > cookies.has("«path/@value»")?cookies.get("«path/@value»"):«arg[1]/@value» |
|
107 otherwise > «arg[1]/@value» |
|
108 } |
|
109 > \n |
|
110 if "position()!=last()" > , |
|
111 } |
|
112 || |
|
113 }; |
|
114 |
|
115 const persistent_locals = new Set([ |
|
116 || |
|
117 foreach "$parsed_widgets/widget[@type='VarInitPersistent']"{ |
|
118 | "«path/@value»"`if "position()!=last()" > ,` |
|
119 } |
|
120 || |
|
121 ]); |
|
122 var persistent_indexes = new Map(); |
|
123 var cache = hmitree_types.map(_ignored => undefined); |
|
124 var updates = new Map(); |
|
125 |
|
126 function page_local_index(varname, pagename){ |
|
127 let pagevars = hmi_locals[pagename]; |
|
128 let new_index; |
|
129 if(pagevars == undefined){ |
|
130 new_index = next_available_index++; |
|
131 hmi_locals[pagename] = {[varname]:new_index} |
|
132 } else { |
|
133 let result = pagevars[varname]; |
|
134 if(result != undefined) { |
|
135 return result; |
|
136 } |
|
137 |
|
138 new_index = next_available_index++; |
|
139 pagevars[varname] = new_index; |
|
140 } |
|
141 let defaultval = local_defaults[varname]; |
|
142 if(defaultval != undefined) { |
|
143 cache[new_index] = defaultval; |
|
144 updates.set(new_index, defaultval); |
|
145 if(persistent_locals.has(varname)) |
|
146 persistent_indexes.set(new_index, varname); |
|
147 } |
|
148 return new_index; |
|
149 } |
|
150 |
|
151 function hmi_local_index(varname){ |
|
152 return page_local_index(varname, "HMI_LOCAL"); |
|
153 } |
|
154 || |
|
155 } |
|
156 |
|
157 emit "preamble:widget-base-class" { |
|
158 || |
|
159 var pending_widget_animates = []; |
|
160 |
|
161 class Widget { |
|
162 offset = 0; |
|
163 frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */ |
|
164 unsubscribable = false; |
|
165 pending_animate = false; |
|
166 |
|
167 constructor(elt_id,args,indexes,minmaxes,members){ |
|
168 this.element_id = elt_id; |
|
169 this.element = id(elt_id); |
|
170 this.args = args; |
|
171 this.indexes = indexes; |
|
172 this.minmaxes = minmaxes; |
|
173 Object.keys(members).forEach(prop => this[prop]=members[prop]); |
|
174 this.lastapply = indexes.map(() => undefined); |
|
175 this.inhibit = indexes.map(() => undefined); |
|
176 this.pending = indexes.map(() => undefined); |
|
177 this.bound_unhinibit = this.unhinibit.bind(this); |
|
178 } |
|
179 |
|
180 unsub(){ |
|
181 /* remove subsribers */ |
|
182 if(!this.unsubscribable) |
|
183 for(let i = 0; i < this.indexes.length; i++) { |
|
184 /* flush updates pending because of inhibition */ |
|
185 let inhibition = this.inhibit[i]; |
|
186 if(inhibition != undefined){ |
|
187 clearTimeout(inhibition); |
|
188 this.lastapply[i] = undefined; |
|
189 this.unhinibit(i); |
|
190 } |
|
191 let index = this.indexes[i]; |
|
192 if(this.relativeness[i]) |
|
193 index += this.offset; |
|
194 subscribers(index).delete(this); |
|
195 } |
|
196 this.offset = 0; |
|
197 this.relativeness = undefined; |
|
198 } |
|
199 |
|
200 sub(new_offset=0, relativeness, container_id){ |
|
201 this.offset = new_offset; |
|
202 this.relativeness = relativeness; |
|
203 this.container_id = container_id ; |
|
204 /* add this's subsribers */ |
|
205 if(!this.unsubscribable) |
|
206 for(let i = 0; i < this.indexes.length; i++) { |
|
207 let index = this.get_variable_index(i); |
|
208 if(index == undefined) continue; |
|
209 subscribers(index).add(this); |
|
210 } |
|
211 need_cache_apply.push(this); |
|
212 } |
|
213 |
|
214 apply_cache() { |
|
215 if(!this.unsubscribable) for(let index in this.indexes){ |
|
216 /* dispatch current cache in newly opened page widgets */ |
|
217 let realindex = this.get_variable_index(index); |
|
218 if(realindex == undefined) continue; |
|
219 let cached_val = cache[realindex]; |
|
220 if(cached_val != undefined) |
|
221 this._dispatch(cached_val, cached_val, index); |
|
222 } |
|
223 } |
|
224 |
|
225 get_variable_index(varnum) { |
|
226 let index = this.indexes[varnum]; |
|
227 if(typeof(index) == "string"){ |
|
228 index = page_local_index(index, this.container_id); |
|
229 } else { |
|
230 if(this.relativeness[varnum]){ |
|
231 index += this.offset; |
|
232 } |
|
233 } |
|
234 return index; |
|
235 } |
|
236 |
|
237 overshot(new_val, max) { |
|
238 } |
|
239 |
|
240 undershot(new_val, min) { |
|
241 } |
|
242 |
|
243 clip_min_max(index, new_val) { |
|
244 let minmax = this.minmaxes[index]; |
|
245 if(minmax !== undefined && typeof new_val == "number") { |
|
246 let [min,max] = minmax; |
|
247 if(new_val < min){ |
|
248 this.undershot(new_val, min); |
|
249 return min; |
|
250 } |
|
251 if(new_val > max){ |
|
252 this.overshot(new_val, max); |
|
253 return max; |
|
254 } |
|
255 } |
|
256 return new_val; |
|
257 } |
|
258 |
|
259 change_hmi_value(index, opstr) { |
|
260 let realindex = this.get_variable_index(index); |
|
261 if(realindex == undefined) return undefined; |
|
262 let old_val = cache[realindex]; |
|
263 let new_val = eval_operation_string(old_val, opstr); |
|
264 new_val = this.clip_min_max(index, new_val); |
|
265 return apply_hmi_value(realindex, new_val); |
|
266 } |
|
267 |
|
268 _apply_hmi_value(index, new_val) { |
|
269 let realindex = this.get_variable_index(index); |
|
270 if(realindex == undefined) return undefined; |
|
271 new_val = this.clip_min_max(index, new_val); |
|
272 return apply_hmi_value(realindex, new_val); |
|
273 } |
|
274 |
|
275 unhinibit(index){ |
|
276 this.inhibit[index] = undefined; |
|
277 let new_val = this.pending[index]; |
|
278 this.pending[index] = undefined; |
|
279 return this.apply_hmi_value(index, new_val); |
|
280 } |
|
281 |
|
282 apply_hmi_value(index, new_val) { |
|
283 if(this.inhibit[index] == undefined){ |
|
284 let now = Date.now(); |
|
285 let min_interval = 1000/this.frequency; |
|
286 let lastapply = this.lastapply[index]; |
|
287 if(lastapply == undefined || now > lastapply + min_interval){ |
|
288 this.lastapply[index] = now; |
|
289 return this._apply_hmi_value(index, new_val); |
|
290 } |
|
291 else { |
|
292 let elapsed = now - lastapply; |
|
293 this.pending[index] = new_val; |
|
294 this.inhibit[index] = setTimeout(this.bound_unhinibit, min_interval - elapsed, index); |
|
295 } |
|
296 } |
|
297 else { |
|
298 this.pending[index] = new_val; |
|
299 return new_val; |
|
300 } |
|
301 } |
|
302 |
|
303 new_hmi_value(index, value, oldval) { |
|
304 // TODO avoid searching, store index at sub() |
|
305 for(let i = 0; i < this.indexes.length; i++) { |
|
306 let refindex = this.get_variable_index(i); |
|
307 if(refindex == undefined) continue; |
|
308 |
|
309 if(index == refindex) { |
|
310 this._dispatch(value, oldval, i); |
|
311 break; |
|
312 } |
|
313 } |
|
314 } |
|
315 |
|
316 _dispatch(value, oldval, varnum) { |
|
317 let dispatch = this.dispatch; |
|
318 if(dispatch != undefined){ |
|
319 try { |
|
320 dispatch.call(this, value, oldval, varnum); |
|
321 } catch(err) { |
|
322 console.log(err); |
|
323 } |
|
324 } |
|
325 } |
|
326 |
|
327 _animate(){ |
|
328 this.animate(); |
|
329 this.pending_animate = false; |
|
330 } |
|
331 |
|
332 request_animate(){ |
|
333 if(!this.pending_animate){ |
|
334 pending_widget_animates.push(this); |
|
335 this.pending_animate = true; |
|
336 requestHMIAnimation(); |
|
337 } |
|
338 |
|
339 } |
|
340 |
|
341 activate_activable(eltsub) { |
|
342 eltsub.inactive.style.display = "none"; |
|
343 eltsub.active.style.display = ""; |
|
344 } |
|
345 |
|
346 inactivate_activable(eltsub) { |
|
347 eltsub.active.style.display = "none"; |
|
348 eltsub.inactive.style.display = ""; |
|
349 } |
|
350 } |
|
351 || |
|
352 } |
|
353 |
|
354 const "excluded_types", "str:split('Page VarInit VarInitPersistent')"; |
|
355 |
|
356 // Key to filter unique types |
|
357 key "TypesKey", "widget", "@type"; |
|
358 |
|
359 emit "declarations:hmi-classes" { |
|
360 const "used_widget_types", """$parsed_widgets/widget[ |
|
361 generate-id() = generate-id(key('TypesKey', @type)) and |
|
362 not(@type = $excluded_types)]"""; |
|
363 apply "$used_widget_types", mode="widget_class"; |
|
364 } |
|
365 |
|
366 template "widget", mode="widget_class" |
|
367 || |
|
368 class «@type»Widget extends Widget{ |
|
369 /* empty class, as «@type» widget didn't provide any */ |
|
370 } |
|
371 || |
|
372 |
|
373 const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types) and not(@id = $discardable_elements/@id)]/@id"; |
|
374 const "hmi_widgets","$hmi_elements[@id = $included_ids]"; |
|
375 const "result_widgets","$result_svg_ns//*[@id = $hmi_widgets/@id]"; |
|
376 |
|
377 emit "declarations:hmi-elements" { |
|
378 | var hmi_widgets = { |
|
379 apply "$hmi_widgets", mode="hmi_widgets"; |
|
380 | } |
|
381 } |
|
382 |
|
383 function "defs_by_labels" { |
|
384 param "labels","''"; |
|
385 param "mandatory","'yes'"; |
|
386 param "subelements","/.."; |
|
387 param "hmi_element"; |
|
388 const "widget_type","@type"; |
|
389 foreach "str:split($labels)" { |
|
390 const "name","."; |
|
391 const "elt","$result_widgets[@id = $hmi_element/@id]//*[@inkscape:label=$name][1]"; |
|
392 choose { |
|
393 when "not($elt/@id)" { |
|
394 if "$mandatory='yes'" { |
|
395 error > «$widget_type» widget must have a «$name» element |
|
396 } |
|
397 // otherwise produce nothing |
|
398 } |
|
399 otherwise { |
|
400 | «$name»_elt: id("«$elt/@id»"), |
|
401 if "$subelements" { |
|
402 | «$name»_sub: { |
|
403 foreach "str:split($subelements)" { |
|
404 const "subname","."; |
|
405 const "subelt","$elt/*[@inkscape:label=$subname][1]"; |
|
406 choose { |
|
407 when "not($subelt/@id)" { |
|
408 if "$mandatory='yes'" { |
|
409 error > «$widget_type» widget must have a «$name»/«$subname» element |
|
410 } |
|
411 | /* missing «$name»/«$subname» element */ |
|
412 } |
|
413 otherwise { |
|
414 | "«$subname»": id("«$subelt/@id»")`if "position()!=last()" > ,` |
|
415 } |
|
416 } |
|
417 } |
|
418 | }, |
|
419 } |
|
420 } |
|
421 } |
|
422 } |
|
423 } |
|
424 |
|
425 def "func:escape_quotes" { |
|
426 param "txt"; |
|
427 // have to use a python string to enter escaped quote |
|
428 // const "frstln", "string-length($frst)"; |
|
429 choose { |
|
430 when !"contains($txt,'\"')"! { |
|
431 result !"concat(substring-before($txt,'\"'),'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!; |
|
432 } |
|
433 otherwise { |
|
434 result "$txt"; |
|
435 } |
|
436 } |
|
437 } |
|
438 |