From 6d192090c8f9156bba3b462f4ff75da2695764bb Mon Sep 17 00:00:00 2001 From: Nikola Simsic Date: Thu, 16 Apr 2026 10:10:30 +0200 Subject: [PATCH] Update parsing of ScrollContainer and TabControl --- mdl/executor/cmd_pages_describe_output.go | 26 ++++++++++++ mdl/executor/cmd_pages_describe_parse.go | 48 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/mdl/executor/cmd_pages_describe_output.go b/mdl/executor/cmd_pages_describe_output.go index 9345f8e0..b1b55b28 100644 --- a/mdl/executor/cmd_pages_describe_output.go +++ b/mdl/executor/cmd_pages_describe_output.go @@ -128,6 +128,32 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) { prefix := strings.Repeat(" ", indent) switch w.Type { + case "Forms$TabControl", "Pages$TabControl": + header := fmt.Sprintf("TABCONTAINER %s", w.Name) + props := appendAppearanceProps(nil, w) + if len(w.Children) > 0 { + formatWidgetProps(e.output, prefix, header, props, " {\n") + for _, child := range w.Children { + e.outputWidgetMDLV3(child, indent+1) + } + fmt.Fprintf(e.output, "%s}\n", prefix) + } else { + formatWidgetProps(e.output, prefix, header, props, "\n") + } + + case "Forms$ScrollContainer", "Pages$ScrollContainer": + header := fmt.Sprintf("SCROLLCONTAINER %s", w.Name) + props := appendAppearanceProps(nil, w) + if len(w.Children) > 0 { + formatWidgetProps(e.output, prefix, header, props, " {\n") + for _, child := range w.Children { + e.outputWidgetMDLV3(child, indent+1) + } + fmt.Fprintf(e.output, "%s}\n", prefix) + } else { + formatWidgetProps(e.output, prefix, header, props, "\n") + } + case "Forms$DivContainer", "Pages$DivContainer": header := fmt.Sprintf("CONTAINER %s", w.Name) props := appendAppearanceProps(nil, w) diff --git a/mdl/executor/cmd_pages_describe_parse.go b/mdl/executor/cmd_pages_describe_parse.go index abb1dd8b..848d01cd 100644 --- a/mdl/executor/cmd_pages_describe_parse.go +++ b/mdl/executor/cmd_pages_describe_parse.go @@ -31,6 +31,54 @@ func (e *Executor) parseRawWidget(w map[string]any, parentEntityContext ...strin typeName, _ := w["$Type"].(string) name, _ := w["Name"].(string) + // ScrollContainer: children live in CenterRegion.Widgets (not Widgets directly). + // We keep the scrollContainer widget ID in the output but also expose all nested + // widgets so DESCRIBE PAGE shows what's inside and the extractor can find them. + if typeName == "Forms$ScrollContainer" || typeName == "Pages$ScrollContainer" { + widget := rawWidget{ + Type: typeName, + Name: name, + } + // Children are nested inside CenterRegion.Widgets + var children []any + if centerRegion, ok := w["CenterRegion"].(map[string]any); ok { + children = getBsonArrayElements(centerRegion["Widgets"]) + } + // Fallback: some older formats may use Widgets directly + if children == nil { + children = getBsonArrayElements(w["Widgets"]) + } + for _, c := range children { + if cMap, ok := c.(map[string]any); ok { + widget.Children = append(widget.Children, e.parseRawWidget(cMap, inheritedCtx)...) + } + } + return []rawWidget{widget} + } + + // TabControl: children live in TabPages[].Widgets — recurse so nested widget IDs + // (e.g. text widgets inside each tab) are included in the output. + if typeName == "Forms$TabControl" || typeName == "Pages$TabControl" { + widget := rawWidget{ + Type: typeName, + Name: name, + } + tabPages := getBsonArrayElements(w["TabPages"]) + for _, tp := range tabPages { + tpMap, ok := tp.(map[string]any) + if !ok { + continue + } + tabWidgets := getBsonArrayElements(tpMap["Widgets"]) + for _, tw := range tabWidgets { + if twMap, ok := tw.(map[string]any); ok { + widget.Children = append(widget.Children, e.parseRawWidget(twMap, inheritedCtx)...) + } + } + } + return []rawWidget{widget} + } + // Parse DivContainer as a proper CONTAINER widget with children if typeName == "Forms$DivContainer" || typeName == "Pages$DivContainer" || typeName == "Forms$GroupBox" || typeName == "Pages$GroupBox" {