--- a/LPCSVGHMI/analyse_widget.xslt Fri Jan 30 08:41:19 2026 +0100
+++ b/LPCSVGHMI/analyse_widget.xslt Tue Mar 31 11:06:21 2026 +0200
@@ -1162,6 +1162,80 @@
<xsl:text>Image display</xsl:text>
+ <xsl:template match="widget[@type='DropDownIndexed']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>DropDownIndexed widget can have one, two or three path variables. + <xsl:text>It needs "text" (svg:text or svg:use referring to svg:text), + <xsl:text>"box" (svg:rect), "button" (svg:*), and "highlight" (svg:rect) + <xsl:text>labeled elements. + <xsl:text>When user clicks on "button", "text" is duplicated to display entries in the + <xsl:text>limit of available space in page, and "box" is extended to contain all + <xsl:text>"highlight" is moved over pre-selected entry. + <xsl:text>The first variable path is index of selection, and the second is value of selection. + <xsl:text>In case there are one or two path variables, a list of texts is defined via + <xsl:text>If there are no arguments, it is expected that "text" labeled element is of + <xsl:text>type svg:use and refers to a svg:text element part of a TextList widget. + <xsl:text>In that case list of texts is set to TextList content. + <xsl:text>When only one argument is given and its value is "#langs" then list of + <xsl:text>texts is automatically set to the human-readable list of supported + <xsl:text>languages by this HMI. + <xsl:text>Otherwise, arguments are used as dropdown options. + <xsl:text>In case there are three path variables, the third path variable is a filter + <xsl:text>in a form of a string containing ':' separated list of indices of the options + <xsl:text>from the arguments that will be shown in the dropdown. + <xsl:text>HMI:DropDownIndexed:Red:Green:Blue:Other@/SELECTED_INDEX@/SELECTED_VALUE + <xsl:text>HMI:DropDownIndexed:Red:Green:Blue:Other@/SELECTED_INDEX@/SELECTED_VALUE@/FILTER + <xsl:text>Let user select text entry in a drop-down menu</xsl:text> + <arg name="entries" count="many" accepts="string"> + <xsl:text>drop-down menu entries</xsl:text> + <path name="selected_index" accepts="HMI_INT"> + <xsl:text>selection index</xsl:text> + <path name="selected_value" accepts="HMI_STRING"> + <xsl:text>selection value</xsl:text> + <path name="filter" accepts="HMI_STRING"> + <xsl:text>indices of shown drop-down menu entries</xsl:text> <xsl:template match="widget[@type='HistoryXYGraph']" mode="widget_desc">
<xsl:value-of select="@type"/>
--- a/LPCSVGHMI/gen_index_xhtml.xslt Fri Jan 30 08:41:19 2026 +0100
+++ b/LPCSVGHMI/gen_index_xhtml.xslt Tue Mar 31 11:06:21 2026 +0200
@@ -9649,6 +9649,217 @@
<xsl:apply-templates mode="inline_svg" select="@*[not(contains(name(), 'href'))] | node()"/>
+ <xsl:template match="widget[@type='DropDownIndexed']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>DropDownIndexed widget can have one, two or three path variables. + <xsl:text>It needs "text" (svg:text or svg:use referring to svg:text), + <xsl:text>"box" (svg:rect), "button" (svg:*), and "highlight" (svg:rect) + <xsl:text>labeled elements. + <xsl:text>When user clicks on "button", "text" is duplicated to display entries in the + <xsl:text>limit of available space in page, and "box" is extended to contain all + <xsl:text>"highlight" is moved over pre-selected entry. + <xsl:text>The first variable path is index of selection, and the second is value of selection. + <xsl:text>In case there are one or two path variables, a list of texts is defined via + <xsl:text>If there are no arguments, it is expected that "text" labeled element is of + <xsl:text>type svg:use and refers to a svg:text element part of a TextList widget. + <xsl:text>In that case list of texts is set to TextList content. + <xsl:text>When only one argument is given and its value is "#langs" then list of + <xsl:text>texts is automatically set to the human-readable list of supported + <xsl:text>languages by this HMI. + <xsl:text>Otherwise, arguments are used as dropdown options. + <xsl:text>In case there are three path variables, the third path variable is a filter + <xsl:text>in a form of a string containing ':' separated list of indices of the options + <xsl:text>from the arguments that will be shown in the dropdown. + <xsl:text>HMI:DropDownIndexed:Red:Green:Blue:Other@/SELECTED_INDEX@/SELECTED_VALUE + <xsl:text>HMI:DropDownIndexed:Red:Green:Blue:Other@/SELECTED_INDEX@/SELECTED_VALUE@/FILTER + <xsl:text>Let user select text entry in a drop-down menu</xsl:text> + <arg name="entries" count="many" accepts="string"> + <xsl:text>drop-down menu entries</xsl:text> + <path name="selected_index" accepts="HMI_INT"> + <xsl:text>selection index</xsl:text> + <path name="selected_value" accepts="HMI_STRING"> + <xsl:text>selection value</xsl:text> + <path name="filter" accepts="HMI_STRING"> + <xsl:text>indices of shown drop-down menu entries</xsl:text> + <xsl:template match="widget[@type='DropDownIndexed']" mode="widget_class"> + <xsl:text>class </xsl:text> + <xsl:text>DropDownIndexedWidget</xsl:text> + <xsl:text> extends Widget{ + <xsl:text> dispatch(value, old_val, index) { + <xsl:text> if (index == 0) { + <xsl:text> if (!this.opened) this.set_selection(value); + <xsl:text> } else if (index == 2) { + <xsl:text> const desiredIndices = value.split(":").map((str) => +str); + <xsl:text> // Cache the original content to prevent data destruction on subsequent filters + <xsl:text> if (!this.original_content) { + <xsl:text> this.original_content = [...this.content]; + <xsl:text> this.content = this.original_content.filter((item, idx) => desiredIndices.includes(idx)); + <declarations:DropDownIndexed/> + <xsl:template match="declarations:DropDownIndexed"> + <xsl:text>/* </xsl:text> + <xsl:value-of select="local-name()"/> + <xsl:text> Object.getOwnPropertyNames(DropDownWidget.prototype).forEach(name => { + <xsl:text> if (name !== "constructor" && name !== "dispatch") { + <xsl:text> DropDownIndexedWidget.prototype[name] = DropDownWidget.prototype[name]; + <xsl:template match="widget[@type='DropDownIndexed']" mode="widget_defs"> + <xsl:param name="hmi_element"/> + <xsl:variable name="disability"> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>/disabled</xsl:text> + <xsl:with-param name="mandatory" select="'no'"/> + <xsl:value-of select="$disability"/> + <xsl:variable name="has_disability" select="string-length($disability)>0"/> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>box button highlight</xsl:text> + <xsl:variable name="text_elt" select="$hmi_element//*[@inkscape:label='text'][1]"/> + <xsl:text>init_specific: function() { + <xsl:when test="count(arg) = 1 and arg[1]/@value = '#langs'"> + <xsl:text> this.text_elt = id("</xsl:text> + <xsl:value-of select="$text_elt/@id"/> + <xsl:text> this.content = langs.map(([lname,lcode]) => lname); + <xsl:when test="count(arg) = 0"> + <xsl:if test="not($text_elt[self::svg:use])"> + <xsl:message terminate="yes"> + <xsl:text>No argument for HMI:DropDownIndexed widget id="</xsl:text> + <xsl:value-of select="$hmi_element/@id"/> + <xsl:text>" and "text" labeled element is not a svg:use element</xsl:text> + <xsl:variable name="real_text_elt" select="$result_widgets[@id = $hmi_element/@id]//*[@original=$text_elt/@id]/svg:text"/> + <xsl:text> this.text_elt = id("</xsl:text> + <xsl:value-of select="$real_text_elt/@id"/> + <xsl:variable name="from_list_id" select="substring-after($text_elt/@xlink:href,'#')"/> + <xsl:variable name="from_list" select="$hmi_textlists[(@id | */@id) = $from_list_id]"/> + <xsl:if test="count($from_list) = 0"> + <xsl:message terminate="yes"> + <xsl:text>HMI:DropDownIndexed widget id="</xsl:text> + <xsl:value-of select="$hmi_element/@id"/> + <xsl:text>" "text" labeled element does not point to a svg:text owned by a HMI:List widget</xsl:text> + <xsl:text> this.content = hmi_widgets["</xsl:text> + <xsl:value-of select="$from_list/@id"/> + <xsl:text> this.text_elt = id("</xsl:text> + <xsl:value-of select="$text_elt/@id"/> + <xsl:text> this.content = [ + <xsl:for-each select="arg"> + <xsl:text> "</xsl:text> + <xsl:value-of select="@value"/> <xsl:template match="widget[@type='HistoryXYGraph']" mode="widget_desc">
<xsl:value-of select="@type"/>
@@ -11255,6 +11466,54 @@
+ <xsl:template match="widget[@type='VarSync']" mode="widget_class"> + <xsl:text>class </xsl:text> + <xsl:text>VarSyncWidget</xsl:text> + <xsl:text> extends Widget{ + <xsl:text> dispatch(value, oldval, varnum) { + <xsl:text> if (varnum === 0) { + <xsl:text> let dest_index = this.get_variable_index(1); + <xsl:text> let current_dest_val = cache[dest_index]; + <xsl:text> if (value !== current_dest_val) { + <xsl:text> this.apply_hmi_value(1, value); + <xsl:text> else if (varnum === 1) { + <xsl:text> let src_index = this.get_variable_index(0); + <xsl:text> if (value !== cache[src_index]) { + <xsl:text> this.apply_hmi_value(0, value); + <xsl:text> this.element.style.display = "none"; <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/LPCSVGHMI/widget_dropdownindexed.ysl2 Tue Mar 31 11:06:21 2026 +0200
@@ -0,0 +1,104 @@
+// widget_dropdownindexed.ysl2 +widget_desc("DropDownIndexed") { + DropDownIndexed widget can have one, two or three path variables. + It needs "text" (svg:text or svg:use referring to svg:text), + "box" (svg:rect), "button" (svg:*), and "highlight" (svg:rect) + When user clicks on "button", "text" is duplicated to display entries in the + limit of available space in page, and "box" is extended to contain all + "highlight" is moved over pre-selected entry. + The first variable path is index of selection, and the second is value of selection. + In case there are one or two path variables, a list of texts is defined via + If there are no arguments, it is expected that "text" labeled element is of + type svg:use and refers to a svg:text element part of a TextList widget. + In that case list of texts is set to TextList content. + When only one argument is given and its value is "#langs" then list of + texts is automatically set to the human-readable list of supported + Otherwise, arguments are used as dropdown options. + In case there are three path variables, the third path variable is a filter + in a form of a string containing ':' separated list of indices of the options + from the arguments that will be shown in the dropdown. + HMI:DropDownIndexed:Red:Green:Blue:Other@/SELECTED_INDEX@/SELECTED_VALUE + HMI:DropDownIndexed:Red:Green:Blue:Other@/SELECTED_INDEX@/SELECTED_VALUE@/FILTER + shortdesc > Let user select text entry in a drop-down menu + arg name="entries" count="many" accepts="string" > drop-down menu entries + path name="selected_index" accepts="HMI_INT" > selection index + path name="selected_value" accepts="HMI_STRING" > selection value + path name="filter" accepts="HMI_STRING" > indices of shown drop-down menu entries +// TODO: support i18n of menu entries using svg:text elements with labels starting with "_" +widget_class("DropDownIndexed") { + dispatch(value, old_val, index) { + if (!this.opened) this.set_selection(value); + } else if (index == 2) { + const desiredIndices = value.split(":").map((str) => +str); + // Cache the original content to prevent data destruction on subsequent filters + if (!this.original_content) { + this.original_content = [...this.content]; + this.content = this.original_content.filter((item, idx) => desiredIndices.includes(idx)); +// Inherit all other methods natively from DropDownWidget to avoid duplication +emit "declarations:DropDownIndexed" + Object.getOwnPropertyNames(DropDownWidget.prototype).forEach(name => { + if (name !== "constructor" && name !== "dispatch") { + DropDownIndexedWidget.prototype[name] = DropDownWidget.prototype[name]; +widget_defs("DropDownIndexed") { + labels("box button highlight"); + // It is assumed that list content conforms to Array interface. + const "text_elt","$hmi_element//*[@inkscape:label='text'][1]"; + | init_specific: function() { + // special case when used for language selection + when "count(arg) = 1 and arg[1]/@value = '#langs'" { + | this.text_elt = id("«$text_elt/@id»"); + | this.content = langs.map(([lname,lcode]) => lname); + if "not($text_elt[self::svg:use])" + error > No argument for HMI:DropDownIndexed widget id="«$hmi_element/@id»" and "text" labeled element is not a svg:use element + const "real_text_elt","$result_widgets[@id = $hmi_element/@id]//*[@original=$text_elt/@id]/svg:text"; + | this.text_elt = id("«$real_text_elt/@id»"); + const "from_list_id", "substring-after($text_elt/@xlink:href,'#')"; + const "from_list", "$hmi_textlists[(@id | */@id) = $from_list_id]"; + if "count($from_list) = 0" + error > HMI:DropDownIndexed widget id="«$hmi_element/@id»" "text" labeled element does not point to a svg:text owned by a HMI:List widget + | this.content = hmi_widgets["«$from_list/@id»"].texts; + | this.text_elt = id("«$text_elt/@id»"); + foreach "arg" | "«@value»", \ No newline at end of file