svghmi/widget_foreach.ysl2
author Edouard Tisserant
Wed, 01 Mar 2023 10:54:54 +0100
changeset 3740 ac0e6de439b5
parent 3603 f1a00aa8cb3b
permissions -rw-r--r--
Linux runtime: overrun detection for real-time timers and for plc execution.

If real-time timer wakes-up PLC thread too late (10% over period), then
warning is logged.

If PLC code (IO retreive, execution, IO publish) takes longer than requested
PLC execution cycle, then warning is logged, and CPU hoogging is mitigated
by delaying next PLC execution a few cylces more until having at least
1ms minimal idle time.
// widget_foreach.ysl2

widget_desc("ForEach") {

    longdesc
    ||
    ForEach widget is used to span a small set of widget over a larger set of
    repeated HMI_NODEs. 

    Idea is somewhat similar to relative page, but it all happens inside the
    ForEach widget, no page involved.

    Together with relative Jump widgets it can be used to build a menu to reach
    relative pages covering many identical HMI_NODES siblings.

    ForEach widget takes a HMI_CLASS name as argument and a HMI_NODE path as
    variable.

    Direct sub-elements can be either groups of widget to be spanned, labeled
    "ClassName:offset", or buttons to control the spanning, labeled
    "ClassName:+/-number".
    ||

    shortdesc > span widgets over a set of repeated HMI_NODEs

    arg name="class_name" accepts="string" > HMI_CLASS name

    path name="root" accepts="HMI_NODE" >  where to find HMI_NODEs whose HMI_CLASS is class_name
}

widget_defs("ForEach") {

    if "count(path) != 1" error > ForEach widget «$hmi_element/@id» must have one HMI path given.
    if "count(arg) != 1" error > ForEach widget «$hmi_element/@id» must have one argument given : a class name.

    const "class","arg[1]/@value";

    const "base_path","path/@value";
    const "hmi_index_base", "$indexed_hmitree/*[@hmipath = $base_path]";
    const "hmi_tree_base", "$hmitree/descendant-or-self::*[@path = $hmi_index_base/@path]";
    const "hmi_tree_items", "$hmi_tree_base/*[@class = $class]";
    const "hmi_index_items", "$indexed_hmitree/*[@path = $hmi_tree_items/@path]"; 
    const "items_paths", "$hmi_index_items/@hmipath"; 
    |     index_pool: [
    foreach "$hmi_index_items" {
    |       «@index»`if "position()!=last()" > ,`
    }
    |     ],
    |     init: function() {
    const "prefix","concat($class,':')";
    const "buttons_regex","concat('^',$prefix,'[+\-][0-9]+')";
    const "buttons", "$hmi_element/*[regexp:test(@inkscape:label, $buttons_regex)]"; 
    foreach "$buttons" {
        const "op","substring-after(@inkscape:label, $prefix)";
    |         id("«@id»").setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_click('«$op»', evt)");
    }
    |
    |         this.items = [
    const "items_regex","concat('^',$prefix,'[0-9]+')";
    const "unordered_items","$hmi_element//*[regexp:test(@inkscape:label, $items_regex)]";
    foreach "$unordered_items" {
        const "elt_label","concat($prefix, string(position()))"; 
        const "elt","$unordered_items[@inkscape:label = $elt_label]";
        const "pos","position()";
        const "item_path", "$items_paths[$pos]";
    |           [ /* item="«$elt_label»" path="«$item_path»" */
        if "count($elt)=0" error > Missing item labeled «$elt_label» in ForEach widget «$hmi_element/@id»
        foreach "func:refered_elements($elt)[@id = $hmi_elements/@id][not(@id = $elt/@id)]" {
            if "not(func:is_descendant_path(func:widget(@id)/path/@value, $item_path))"
                error > Widget id="«@id»" label="«@inkscape:label»" is having wrong path. Accroding to ForEach widget ancestor id="«$hmi_element/@id»", path should be descendant of "«$item_path»".
    |             hmi_widgets["«@id»"]`if "position()!=last()" > ,`
        }
    |           ]`if "position()!=last()" > ,`
    }
    |         ]
    |     },
    |     item_offset: 0,
}

widget_class("ForEach")
||

    unsub_items(){
        for(let item of this.items){
            for(let widget of item) {
                widget.unsub();
            }
        }
    }

    unsub(){
        this.unsub_items();
        this.offset = 0;
        this.relativeness = undefined;
    }

    sub_items(){
        for(let i = 0; i < this.items.length; i++) {
            let item = this.items[i];
            let orig_item_index = this.index_pool[i];
            let item_index = this.index_pool[i+this.item_offset];
            let item_index_offset = item_index - orig_item_index;
            if(this.relativeness[0])
                item_index_offset += this.offset;
            for(let widget of item) {
                /* all variables of all widgets in a ForEach are all relative. 
                   Really.

                   TODO: allow absolute variables in ForEach widgets
                */
                widget.sub(item_index_offset, widget.indexes.map(_=>true));
            }
        }
    }

    sub(new_offset=0, relativeness=[]){
        this.offset = new_offset;
        this.relativeness = relativeness;
        this.sub_items();
    }

    apply_cache() {
        this.items.forEach(item=>item.forEach(widget=>widget.apply_cache()));
    }

    on_click(opstr, evt) {
        let new_item_offset = eval(String(this.item_offset)+opstr);
        if(new_item_offset + this.items.length > this.index_pool.length) {
            if(this.item_offset + this.items.length == this.index_pool.length)
                new_item_offset = 0;
            else
                new_item_offset = this.index_pool.length - this.items.length;
        } else if(new_item_offset < 0) {
            if(this.item_offset == 0)
                new_item_offset = this.index_pool.length - this.items.length;
            else
                new_item_offset = 0;
        }
        this.item_offset = new_item_offset;
        this.unsub_items();
        this.sub_items();
        update_subscriptions();
        this.apply_cache(); 
        jumps_need_update = true;
        requestHMIAnimation();
    }
||