19 with "mandatory","'warn'"; |
19 with "mandatory","'warn'"; |
20 content; |
20 content; |
21 } |
21 } |
22 }; |
22 }; |
23 |
23 |
|
24 decl _activable(*level) alias - { |
|
25 | activable_sub:{ |
|
26 const "activity" labels("/active /inactive") { |
|
27 with "mandatory"{text *level}; |
|
28 content; |
|
29 } |
|
30 value "$activity"; |
|
31 const "has_activity","string-length($activity)>0"; |
|
32 | }, |
|
33 | has_activity: «$has_activity», |
|
34 }; |
|
35 |
24 decl activable() alias - { |
36 decl activable() alias - { |
25 | activable_sub:{ |
37 _activable("warn") |
26 warning_labels("/active /inactive") { |
38 }; |
27 content; |
39 decl optional_activable() alias - { |
28 } |
40 _activable("no") |
29 | } |
41 }; |
30 }; |
42 |
31 decl activable_labels(*ptr) alias - { |
43 decl activable_labels(*ptr) alias - { |
32 optional_labels(*ptr) { |
44 optional_labels(*ptr) { |
33 with "subelements","'active inactive'"; |
45 with "subelements","'active inactive'"; |
34 content; |
46 content; |
35 } |
47 } |
63 template "svg:*", mode="hmi_widgets" { |
79 template "svg:*", mode="hmi_widgets" { |
64 const "widget", "func:widget(@id)"; |
80 const "widget", "func:widget(@id)"; |
65 const "eltid","@id"; |
81 const "eltid","@id"; |
66 const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,` |
82 const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,` |
67 const "indexes" foreach "$widget/path" { |
83 const "indexes" foreach "$widget/path" { |
|
84 if "position()!=last()" > , |
|
85 } |
|
86 |
|
87 const "variables" foreach "$widget/path" { |
|
88 > [ |
68 choose { |
89 choose { |
69 when "not(@index)" { |
90 when "not(@index)" { |
70 choose { |
91 choose { |
71 when "not(@type)" { |
92 when "not(@type)" { |
72 warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree |
93 warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree |
82 } |
103 } |
83 otherwise { |
104 otherwise { |
84 > «@index» |
105 > «@index» |
85 } |
106 } |
86 } |
107 } |
87 if "position()!=last()" > , |
108 > , { |
88 } |
109 if "@min and @max"{ |
89 |
110 > minmax:[«@min», «@max»] |
90 const "minmaxes" foreach "$widget/path" { |
111 if "@assign" |
91 choose { |
112 > , |
92 when "@min and @max" |
113 } |
93 > [«@min»,«@max»] |
114 if "@assign" |
94 otherwise |
115 > assign:"«@assign»" |
95 > undefined |
116 > }] |
96 } |
|
97 if "position()!=last()" > , |
117 if "position()!=last()" > , |
98 } |
118 } |
99 |
119 |
100 const "freq" choose { |
120 const "freq" choose { |
101 when "$widget/@freq" |
121 when "$widget/@freq" |
102 > "«$widget/@freq»" |
122 > "«$widget/@freq»" |
103 otherwise |
123 otherwise |
104 > undefined |
124 > undefined |
105 } |
125 } |
106 |
126 |
107 | "«@id»": new «$widget/@type»Widget ("«@id»",«$freq»,[«$args»],[«$indexes»],[«$minmaxes»],{ |
127 const "enable_expr" choose{ |
|
128 when "$widget/@enable_expr" |
|
129 > true |
|
130 otherwise |
|
131 > false |
|
132 } |
|
133 |
|
134 | "«@id»": new «$widget/@type»Widget ("«@id»",«$freq»,[«$args»],[«$variables»],«$enable_expr»,{ |
|
135 if "$widget/@enable_expr" { |
|
136 |
|
137 | assignments: [], |
|
138 | compute_enable: function(value, oldval, varnum) { |
|
139 | let result = false; |
|
140 | do { |
|
141 foreach "$widget/path" { |
|
142 const "varid","generate-id()"; |
|
143 const "varnum","position()-1"; |
|
144 if "@assign" foreach "$widget/path[@assign]" if "$varid = generate-id()" { |
|
145 | if(varnum == «$varnum») this.assignments[«position()-1»] = value; |
|
146 | let «@assign» = this.assignments[«position()-1»]; |
|
147 | if(«@assign» == undefined) break; |
|
148 } |
|
149 } |
|
150 | result = «$widget/@enable_expr»; |
|
151 | } while(0); |
|
152 | this.enable(result); |
|
153 | }, |
|
154 } |
108 apply "$widget", mode="widget_defs" with "hmi_element","."; |
155 apply "$widget", mode="widget_defs" with "hmi_element","."; |
109 | })`if "position()!=last()" > ,` |
156 | })`if "position()!=last()" > ,` |
110 } |
157 } |
111 |
158 |
112 emit "preamble:local-variable-indexes" { |
159 emit "preamble:local-variable-indexes" { |
185 } |
232 } |
186 function _show(elt, placeholder){ |
233 function _show(elt, placeholder){ |
187 placeholder.parentNode.insertBefore(elt, placeholder); |
234 placeholder.parentNode.insertBefore(elt, placeholder); |
188 } |
235 } |
189 |
236 |
190 function set_activation_state(eltsub, state){ |
237 function set_activity_state(eltsub, state){ |
191 if(eltsub.active_elt != undefined){ |
238 if(eltsub.active_elt != undefined){ |
192 if(eltsub.active_elt_placeholder == undefined){ |
239 if(eltsub.active_elt_placeholder == undefined){ |
193 eltsub.active_elt_placeholder = document.createComment(""); |
240 eltsub.active_elt_placeholder = document.createComment(""); |
194 eltsub.active_elt.parentNode.insertBefore(eltsub.active_elt_placeholder, eltsub.active_elt); |
241 eltsub.active_elt.parentNode.insertBefore(eltsub.active_elt_placeholder, eltsub.active_elt); |
195 } |
242 } |
200 eltsub.inactive_elt_placeholder = document.createComment(""); |
247 eltsub.inactive_elt_placeholder = document.createComment(""); |
201 eltsub.inactive_elt.parentNode.insertBefore(eltsub.inactive_elt_placeholder, eltsub.inactive_elt); |
248 eltsub.inactive_elt.parentNode.insertBefore(eltsub.inactive_elt_placeholder, eltsub.inactive_elt); |
202 } |
249 } |
203 ((state || state==undefined)?_hide:_show)(eltsub.inactive_elt, eltsub.inactive_elt_placeholder); |
250 ((state || state==undefined)?_hide:_show)(eltsub.inactive_elt, eltsub.inactive_elt_placeholder); |
204 } |
251 } |
205 } |
|
206 |
|
207 function activate_activable(eltsub) { |
|
208 set_activation_state(eltsub, true); |
|
209 } |
|
210 |
|
211 function inactivate_activable(eltsub) { |
|
212 set_activation_state(eltsub, false); |
|
213 } |
252 } |
214 |
253 |
215 class Widget { |
254 class Widget { |
216 offset = 0; |
255 offset = 0; |
217 frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */ |
256 frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */ |
218 unsubscribable = false; |
257 unsubscribable = false; |
219 pending_animate = false; |
258 pending_animate = false; |
220 |
259 |
221 constructor(elt_id, freq, args, indexes, minmaxes, members){ |
260 constructor(elt_id, freq, args, variables, enable_expr, members){ |
222 this.element_id = elt_id; |
261 this.element_id = elt_id; |
223 this.element = id(elt_id); |
262 this.element = id(elt_id); |
224 this.args = args; |
263 this.args = args; |
225 this.indexes = indexes; |
264 |
226 this.minmaxes = minmaxes; |
265 [this.indexes, this.variables_options] = (variables.length>0) ? zip(...variables) : [[],[]]; |
|
266 this.indexes_length = this.indexes.length; |
|
267 |
|
268 this.enable_expr = enable_expr; |
|
269 this.enable_state = true; |
|
270 this.enable_displayed_state = true; |
|
271 this.enabled_elts = []; |
|
272 |
227 Object.keys(members).forEach(prop => this[prop]=members[prop]); |
273 Object.keys(members).forEach(prop => this[prop]=members[prop]); |
228 this.lastapply = indexes.map(() => undefined); |
274 this.lastapply = this.indexes.map(() => undefined); |
229 this.inhibit = indexes.map(() => undefined); |
275 this.inhibit = this.indexes.map(() => undefined); |
230 this.pending = indexes.map(() => undefined); |
276 this.pending = this.indexes.map(() => undefined); |
231 this.bound_uninhibit = this.uninhibit.bind(this); |
277 this.bound_uninhibit = this.uninhibit.bind(this); |
232 |
278 |
233 this.lastdispatch = indexes.map(() => undefined); |
279 this.lastdispatch = this.indexes.map(() => undefined); |
234 this.deafen = indexes.map(() => undefined); |
280 this.deafen = this.indexes.map(() => undefined); |
235 this.incoming = indexes.map(() => undefined); |
281 this.incoming = this.indexes.map(() => undefined); |
236 this.bound_undeafen = this.undeafen.bind(this); |
282 this.bound_undeafen = this.undeafen.bind(this); |
237 |
283 |
238 this.forced_frequency = freq; |
284 this.forced_frequency = freq; |
239 this.clip = true; |
285 this.clip = true; |
240 } |
286 } |
265 init.call(this); |
311 init.call(this); |
266 } catch(err) { |
312 } catch(err) { |
267 console.log(err); |
313 console.log(err); |
268 } |
314 } |
269 } |
315 } |
|
316 |
|
317 if(this.enable_expr){ |
|
318 this.enable_state = false; |
|
319 this.enable_displayed_state = false; |
|
320 for(let child of Array.from(this.element.children)){ |
|
321 let label = child.getAttribute("inkscape:label"); |
|
322 if(label!="disabled"){ |
|
323 this.enabled_elts.push(child); |
|
324 this.element.removeChild(child); |
|
325 } |
|
326 } |
|
327 } |
270 } |
328 } |
271 |
329 |
272 unsub(){ |
330 unsub(){ |
273 /* remove subsribers */ |
331 /* remove subsribers */ |
274 if(!this.unsubscribable) |
332 for(let i = 0; i < this.indexes_length; i++) { |
275 for(let i = 0; i < this.indexes.length; i++) { |
333 /* flush updates pending because of inhibition */ |
276 /* flush updates pending because of inhibition */ |
334 let inhibition = this.inhibit[i]; |
277 let inhibition = this.inhibit[i]; |
335 if(inhibition != undefined){ |
278 if(inhibition != undefined){ |
336 clearTimeout(inhibition); |
279 clearTimeout(inhibition); |
337 this.lastapply[i] = undefined; |
280 this.lastapply[i] = undefined; |
338 this.uninhibit(i); |
281 this.uninhibit(i); |
339 } |
282 } |
340 let deafened = this.deafen[i]; |
283 let deafened = this.deafen[i]; |
341 if(deafened != undefined){ |
284 if(deafened != undefined){ |
342 clearTimeout(deafened); |
285 clearTimeout(deafened); |
343 this.lastdispatch[i] = undefined; |
286 this.lastdispatch[i] = undefined; |
344 this.undeafen(i); |
287 this.undeafen(i); |
345 } |
288 } |
346 let index = this.get_variable_index(i); |
289 let index = this.indexes[i]; |
347 subscribers(index).delete(this); |
290 if(this.relativeness[i]) |
348 } |
291 index += this.offset; |
|
292 subscribers(index).delete(this); |
|
293 } |
|
294 this.offset = 0; |
349 this.offset = 0; |
295 this.relativeness = undefined; |
350 this.relativeness = undefined; |
296 } |
351 } |
297 |
352 |
298 sub(new_offset=0, relativeness, container_id){ |
353 sub(new_offset=0, relativeness, container_id){ |
299 this.offset = new_offset; |
354 this.offset = new_offset; |
300 this.relativeness = relativeness; |
355 this.relativeness = relativeness; |
301 this.container_id = container_id ; |
356 this.container_id = container_id ; |
302 /* add this's subsribers */ |
357 /* add this's subsribers */ |
303 if(!this.unsubscribable) |
358 for(let i = 0; i < this.indexes_length; i++) { |
304 for(let i = 0; i < this.indexes.length; i++) { |
359 let index = this.get_variable_index(i); |
305 let index = this.get_variable_index(i); |
360 if(index == undefined) continue; |
306 if(index == undefined) continue; |
361 subscribers(index).add(this); |
307 subscribers(index).add(this); |
362 } |
308 } |
363 this.apply_cache(); |
309 need_cache_apply.push(this); |
|
310 } |
364 } |
311 |
365 |
312 apply_cache() { |
366 apply_cache() { |
313 if(!this.unsubscribable) for(let index in this.indexes){ |
367 for(let i = 0; i < this.indexes_length; i++) { |
314 /* dispatch current cache in newly opened page widgets */ |
368 /* dispatch current cache in newly opened page widgets */ |
315 let realindex = this.get_variable_index(index); |
369 let realindex = this.get_variable_index(i); |
316 if(realindex == undefined) continue; |
370 if(realindex == undefined) continue; |
317 let cached_val = cache[realindex]; |
371 let cached_val = cache[realindex]; |
318 if(cached_val != undefined) |
372 if(cached_val != undefined) |
319 this._dispatch(cached_val, cached_val, index); |
373 this.feed_data_for_dispatch(cached_val, cached_val, i); |
320 } |
374 } |
321 } |
375 } |
322 |
376 |
323 get_variable_index(varnum) { |
377 get_variable_index(varnum) { |
324 let index = this.indexes[varnum]; |
378 let index = this.indexes[varnum]; |
400 } |
454 } |
401 } |
455 } |
402 |
456 |
403 new_hmi_value(index, value, oldval) { |
457 new_hmi_value(index, value, oldval) { |
404 // TODO avoid searching, store index at sub() |
458 // TODO avoid searching, store index at sub() |
405 for(let i = 0; i < this.indexes.length; i++) { |
459 for(let i = 0; i < this.indexes_length; i++) { |
406 let refindex = this.get_variable_index(i); |
460 let refindex = this.get_variable_index(i); |
407 if(refindex == undefined) continue; |
461 if(refindex == undefined) continue; |
408 |
462 |
409 if(index == refindex) { |
463 if(index == refindex) { |
410 this._dispatch(value, oldval, i); |
464 this.feed_data_for_dispatch(value, oldval, i); |
411 break; |
465 break; |
412 } |
466 } |
413 } |
467 } |
414 } |
468 } |
415 |
469 |
416 undeafen(index){ |
470 undeafen(index){ |
417 this.deafen[index] = undefined; |
471 this.deafen[index] = undefined; |
418 let [new_val, old_val] = this.incoming[index]; |
472 let [new_val, old_val] = this.incoming[index]; |
419 this.incoming[index] = undefined; |
473 this.incoming[index] = undefined; |
420 this.dispatch(new_val, old_val, index); |
474 this.do_dispatch(new_val, old_val, index); |
421 } |
475 } |
422 |
476 |
423 _dispatch(value, oldval, varnum) { |
477 enable(enabled){ |
424 let dispatch = this.dispatch; |
478 if(this.enable_state != enabled){ |
425 if(dispatch != undefined){ |
479 this.enable_state = enabled; |
|
480 this.request_animate(); |
|
481 } |
|
482 } |
|
483 |
|
484 animate_enable(){ |
|
485 if(this.enable_state && !this.enable_displayed_state){ |
|
486 //show widget |
|
487 for(let child of this.enabled_elts){ |
|
488 this.element.appendChild(child); |
|
489 } |
|
490 |
|
491 //hide disabled content |
|
492 if(this.disabled_elt && this.disabled_elt.parentNode != null) |
|
493 this.element.removeChild(this.disabled_elt); |
|
494 |
|
495 this.enable_displayed_state = true; |
|
496 |
|
497 }else if(!this.enable_state && this.enable_displayed_state){ |
|
498 |
|
499 //hide widget |
|
500 for(let child of this.enabled_elts){ |
|
501 if(child.parentNode != null) |
|
502 this.element.removeChild(child); |
|
503 } |
|
504 |
|
505 //show disabled content |
|
506 if(this.disabled_elt) |
|
507 this.element.appendChild(this.disabled_elt); |
|
508 |
|
509 this.enable_displayed_state = false; |
|
510 |
|
511 // once disabled activity display is lost |
|
512 this.activity_displayed_state = undefined; |
|
513 } |
|
514 } |
|
515 |
|
516 feed_data_for_dispatch(value, oldval, varnum) { |
|
517 if(this.dispatch || this.enable_expr){ |
426 if(this.deafen[varnum] == undefined){ |
518 if(this.deafen[varnum] == undefined){ |
427 let now = Date.now(); |
519 let now = Date.now(); |
428 let min_interval = 1000/this.frequency; |
520 let min_interval = 1000/this.frequency; |
429 let lastdispatch = this.lastdispatch[varnum]; |
521 let lastdispatch = this.lastdispatch[varnum]; |
430 if(lastdispatch == undefined || now > lastdispatch + min_interval){ |
522 if(lastdispatch == undefined || now > lastdispatch + min_interval){ |
431 this.lastdispatch[varnum] = now; |
523 this.lastdispatch[varnum] = now; |
432 try { |
524 this.do_dispatch(value, oldval, varnum) |
433 dispatch.call(this, value, oldval, varnum); |
|
434 } catch(err) { |
|
435 console.log(err); |
|
436 } |
|
437 } |
525 } |
438 else { |
526 else { |
439 let elapsed = now - lastdispatch; |
527 let elapsed = now - lastdispatch; |
440 this.incoming[varnum] = [value, oldval]; |
528 this.incoming[varnum] = [value, oldval]; |
441 this.deafen[varnum] = setTimeout(this.bound_undeafen, min_interval - elapsed, varnum); |
529 this.deafen[varnum] = setTimeout(this.bound_undeafen, min_interval - elapsed, varnum); |
445 this.incoming[varnum] = [value, oldval]; |
533 this.incoming[varnum] = [value, oldval]; |
446 } |
534 } |
447 } |
535 } |
448 } |
536 } |
449 |
537 |
|
538 do_dispatch(value, oldval, varnum) { |
|
539 if(this.dispatch) try { |
|
540 this.dispatch(value, oldval, varnum); |
|
541 } catch(err) { |
|
542 console.log(err); |
|
543 } |
|
544 if(this.enable_expr) try { |
|
545 this.compute_enable(value, oldval, varnum); |
|
546 } catch(err) { |
|
547 console.log(err); |
|
548 } |
|
549 } |
|
550 |
450 _animate(){ |
551 _animate(){ |
451 this.animate(); |
552 if(this.enable_expr) |
|
553 this.animate_enable(); |
|
554 // inhibit widget animation when disabled |
|
555 if(!this.enable_expr || this.enable_state){ |
|
556 if(this.has_activity) |
|
557 this.animate_activity(); |
|
558 if(this.animate != undefined) |
|
559 this.animate(); |
|
560 } |
452 this.pending_animate = false; |
561 this.pending_animate = false; |
453 } |
562 } |
454 |
563 |
455 request_animate(){ |
564 request_animate(){ |
456 if(!this.pending_animate){ |
565 if(!this.pending_animate){ |