svghmi/detachable_pages.ysl2
author Edouard Tisserant
Thu, 17 Nov 2022 11:08:36 +0100
changeset 3682 c613afdab571
parent 3653 d5ff60e906b0
child 3685 570a738239f4
permissions -rw-r--r--
IDE: Optimization of modification events processing in text editors.

Too many modifications types where registered, and then too many events were fired.
Also, in case of uninterrupted sequence of events, updates to the model is deferred to the end of that sequence (wx.Callafter).
// detachable_pages.ysl2
//
// compute what elements are required by pages
// and decide where to cut when removing/attaching
// pages elements on page switch

const "hmi_pages_descs", "$parsed_widgets/widget[@type = 'Page']";
const "hmi_pages", "$hmi_elements[@id = $hmi_pages_descs/@id]";

const "default_page" choose {
    when "count($hmi_pages) > 1" {
        choose {
            when "$hmi_pages_descs/arg[1]/@value = 'Home'" > Home
            otherwise {
                error > No Home page defined!
            }
        }
    }
    when "count($hmi_pages) = 0" {
        error > No page defined!
    }
    otherwise > «func:widget($hmi_pages/@id)/arg[1]/@value»
}

emit "preamble:default-page" {
    |
    | var default_page = "«$default_page»";
    const "screensaverpage", "$hmi_pages_descs[arg[1]/@value = 'ScreenSaver']";
    const "delay" choose {
        when "$screensaverpage" {
            const "delaystr", "$screensaverpage/arg[2]/@value";
            if "not(regexp:test($delaystr,'^[0-9]+$'))"
                error > ScreenSaver page has missing or malformed delay argument.
            value "$delaystr";
        }
        otherwise > null
    }
    | var screensaver_delay = «$delay»;
}

const "keypads_descs", "$parsed_widgets/widget[@type = 'Keypad']";
const "keypads", "$hmi_elements[@id = $keypads_descs/@id]";

// returns all directly or indirectly refered elements
def "func:refered_elements" {
    param "elems";
    const "descend", "$elems/descendant-or-self::svg:*";
    const "clones", "$descend[self::svg:use]";
    const "originals", "//svg:*[concat('#',@id) = $clones/@xlink:href]";
    choose {
        when "$originals"
            result "$descend | func:refered_elements($originals)";
        otherwise
            result "$descend";
    }
}

// variable "overlapping_geometry" was added for optimization.
// It avoids calling func:overlapping_geometry 3 times for each page
// (apparently libxml doesn't cache exslt function results)
// in order to optimize further, func:overlapping_geometry 
// should be implemented in python or even C,
// as this is still the main bottleneck here
const "_overlapping_geometry" {
    foreach "$hmi_pages | $keypads" {
        const "k", "concat('overlapping:', @id)";
        value "ns:ProgressStart($k, concat('collecting membership of ', @inkscape:label))";
        elt {
            attrib "id" > «@id»
            copy "func:overlapping_geometry(.)";
        }
        value "ns:ProgressEnd($k)";
    }
}

const "overlapping_geometry", "exsl:node-set($_overlapping_geometry)";

def "func:all_related_elements" {
    param "page";
    const "page_overlapping_geometry", "$overlapping_geometry/elt[@id = $page/@id]/*";
    const "page_overlapping_elements", "//svg:*[@id = $page_overlapping_geometry/@Id]";
    const "page_widgets_elements", """
        $hmi_elements[not(@id=$page/@id)
                      and descendant-or-self::svg:*/@id = $page_overlapping_elements/@id]
        /descendant-or-self::svg:*""";
    const "page_sub_elements", "func:refered_elements($page | $page_overlapping_elements | $page_widgets_elements)";
    result "$page_sub_elements";
}


def "func:required_elements" {
    param "pages"; 
    choose{
        when "$pages"{
            result """func:all_related_elements($pages[1])
                      | func:required_elements($pages[position()!=1])""";
        }otherwise{
            result "/..";
        }
    }
}

const "required_page_elements",
    "func:required_elements($hmi_pages | $keypads)/ancestor-or-self::svg:*";

const "required_list_elements", "func:refered_elements(($hmi_lists | $hmi_textlists)[@id = $required_page_elements/@id])/ancestor-or-self::svg:*";

const "required_elements", "$defs | $required_list_elements | $required_page_elements";

const "discardable_elements", "//svg:*[not(@id = $required_elements/@id)]";

def "func:sumarized_elements" {
    param "elements";
    const "short_list", "$elements[not(ancestor::*/@id = $elements/@id)]";
    const "filled_groups", """$short_list/parent::*[
        not(child::*[
            not(@id = $discardable_elements/@id) and
            not(@id = $short_list/@id)
        ])]""";
    const "groups_to_add", "$filled_groups[not(ancestor::*/@id = $filled_groups/@id)]";
    result "$groups_to_add | $short_list[not(ancestor::*/@id = $filled_groups/@id)]";
}

def "func:detachable_elements" {
    param "pages";
    choose{
        when "$pages"{
            result """func:sumarized_elements(func:all_related_elements($pages[1]))
                      | func:detachable_elements($pages[position()!=1])""";
        }otherwise{
            result "/..";
        }
    }
}

// Avoid nested detachables
const "_detachable_elements", "func:detachable_elements($hmi_pages | $keypads)";
const "detachable_elements", "$_detachable_elements[not(ancestor::*/@id = $_detachable_elements/@id)]";

emit "declarations:detachable-elements" {
    |
    | var detachable_elements = {
    foreach "$detachable_elements"{
    |     "«@id»":[id("«@id»"), id("«../@id»")]`if "position()!=last()" > ,`
    }
    | }
}

const "forEach_widgets_ids", "$parsed_widgets/widget[@type = 'ForEach']/@id";
const "forEach_widgets", "$hmi_widgets[@id = $forEach_widgets_ids]";
const "in_forEach_widget_ids", "func:refered_elements($forEach_widgets)[not(@id = $forEach_widgets_ids)]/@id";

template "svg:*", mode="page_desc" {
    if "ancestor::*[@id = $hmi_pages/@id]" error > HMI:Page «@id» is nested in another HMI:Page


    const "desc", "func:widget(@id)";
    const "pagename", "$desc/arg[1]/@value";
    const "msg", "concat('generating page description ', $pagename)";
    value "ns:ProgressStart($pagename, $msg)";
    const "page", ".";
    const "p", "$geometry[@Id = $page/@id]";

    const "page_all_elements", "func:all_related_elements($page)";

    const "all_page_widgets","$hmi_widgets[@id = $page_all_elements/@id and @id != $page/@id]";
    const "page_managed_widgets","$all_page_widgets[not(@id=$in_forEach_widget_ids)]";
    const "page_relative_widgets",
        "$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $desc/path/@value)]";

    // Take closest ancestor in detachable_elements
    // since nested detachable elements are filtered out
    const "sumarized_page", 
        """func:sumarized_elements($page_all_elements)""";

    const "required_detachables", 
        """$sumarized_page/
           ancestor-or-self::*[@id = $detachable_elements/@id]""";

    |   "«$pagename»": {
    //|     widget: hmi_widgets["«@id»"],
    |     bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»],
    if "$desc/path/@value" {
        if "count($desc/path/@index)=0"
            warning > Page id="«$page/@id»" : No match for path "«$desc/path/@value»" in HMI tree
    |     page_index: «$desc/path/@index»,
    |     page_class: "«$indexed_hmitree/*[@hmipath = $desc/path/@value]/@class»",
    }
    |     widgets: [
    foreach "$page_managed_widgets" {
        const "widget_paths_relativeness" 
            foreach "func:widget(@id)/path" {
                value "func:is_descendant_path(@value, $desc/path/@value)";
                if "position()!=last()" > ,
            }
    |         [hmi_widgets["«@id»"], [«$widget_paths_relativeness»]]`if "position()!=last()" > ,`
    }
    |     ],
    |     jumps: [
    foreach "$parsed_widgets/widget[@id = $all_page_widgets/@id and @type='Jump']" {
    |         hmi_widgets["«@id»"]`if "position()!=last()" > ,`
    }
    |     ],
    |     required_detachables: {
    foreach "$required_detachables" {
    |         "«@id»": detachable_elements["«@id»"]`if "position()!=last()" > ,`
    }
    |     }
    apply "$parsed_widgets/widget[@id = $all_page_widgets/@id]", mode="widget_page"{
        with "page_desc", "$desc";
    }
    |   }`if "position()!=last()" > ,`
    value "ns:ProgressEnd($pagename)";
}

emit "definitions:page-desc" {
    |
    | var page_desc = {
    apply "$hmi_pages", mode="page_desc";
    | }
}

template "*", mode="widget_page";


emit "debug:detachable-pages" {
    |
    | DETACHABLES:
    foreach "$detachable_elements"{
        |  «@id»
    }
    | DISCARDABLES:
    foreach "$discardable_elements"{
        |  «@id»
    }
    | In Foreach:
    foreach "$in_forEach_widget_ids"{
        |  «.»
    }
    | Overlapping 
    apply "$overlapping_geometry", mode="testtree";
}