svghmi/detachable_pages.ysl2
author Edouard Tisserant <edouard.tisserant@gmail.com>
Sun, 12 Mar 2023 00:51:53 +0100
branchwxPython4
changeset 3746 41be039fbb8c
parent 3685 570a738239f4
child 3877 ecb947e5bbb4
permissions -rw-r--r--
IDE: fix again ruberband with gtk3.

DC logical functions are now disabled when using GTK3.
Apparently using XOR was still having an effect.
Use regular black pen with no logical funciton instead.
// 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:page-class" {
    | class PageWidget extends Widget{}
}

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_root_path", "$desc/path[not(@assign)]";
    if "count($page_root_path)>1"
        error > Page id="«$page/@id»" : only one root path can be declared

    const "page_relative_widgets",
        "$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $page_root_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»": {
    |     bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»],
    if "count($page_root_path)=1"{
        if "count($page_root_path/@index)=0"
            warning > Page id="«$page/@id»" : No match for path "«$page_root_path/@value»" in HMI tree
    |     page_index: «$page_root_path/@index»,
    |     page_class: "«$indexed_hmitree/*[@hmipath = $page_root_path/@value]/@class»",
    }
    |     widgets: [
    |         [hmi_widgets["«$page/@id»"], []],
    foreach "$page_managed_widgets" {
        const "widget_paths_relativeness" 
            foreach "func:widget(@id)/path" {
                value "func:is_descendant_path(@value, $page_root_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";
}