Wednesday, April 08, 2026

Generate Graphvix DOT Files from Gnocl Widget Heirarchies

The innards of complex GUI layouts are often difficult to understand. The newly added gnocl::widgetTree will generate a txt visualization of the GUI layout. These text files can then be converted to Graphvix .DOT files from which a png image be generated.  

 

# tree_to_dot.tcl 
#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

#REF: tree_to_graphviz_custom_out.tcl
# Description: Converts GTK tree to DOT with stacked labels and a custom -o output option.

proc parse_tree_to_dot {input_text} {
    set lines [split [string trim $input_text] "\n"]
    set dot_output "digraph GTK_Tree \{\n"
    append dot_output "    node \[shape=box, fontname=\"Helvetica\", fontsize=10, style=\"rounded,filled\", fillcolor=\"white\"\];\n"
    append dot_output "    rankdir=LR;\n"

    array set last_node_at_level {}
    set node_counter 0

    foreach line $lines {
        if {![regexp -indices {[\w\(]} $line match]} continue
        set start_idx [lindex $match 0]
        
        set full_label [string trim [string range $line $start_idx end]]
        
        # Regex to separate Class and Name/ID for stacked labeling
        if {[regexp {^([^\s\(]+)\s*(.*)$} $full_label -> class extra]} {
            set display_label [expr {$extra ne "" ? "$class\n$extra" : $class}]
        } else {
            set display_label $full_label
        }

        set safe_label [string map {\" {\"}} $display_label]
        set current_id "node_[incr node_counter]"
        append dot_output "    $current_id \[label=\"$safe_label\"\];\n"

        set current_depth $start_idx
        set parent_id ""
        set levels [lsort -integer -decreasing [array names last_node_at_level]]
        
        foreach lvl $levels {
            if {$lvl < $current_depth} {
                set parent_id $last_node_at_level($lvl)
                break
            }
        }

        if {$parent_id ne ""} {
            append dot_output "    $parent_id -> $current_id;\n"
        }

        set last_node_at_level($current_depth) $current_id
        foreach lvl [array names last_node_at_level] {
            if {$lvl > $current_depth} { unset last_node_at_level($lvl) }
        }
    }
    append dot_output "\}"
    return $dot_output
}

# --- COMMAND LINE PROCESSING ---

set infile ""
set outfile "output.dot"

for {set i 0} {$i < [llength $argv]} {incr i} {
    set arg [lindex $argv $i]
    switch -exact -- $arg {
        "-o" {
            incr i
            set outfile [lindex $argv $i]
        }
        default {
            set infile $arg
        }
    }
}

# --- FINALIZATION & PREVIEW ---

if {$infile ne "" && [file exists $infile]} {
    set fp [open $infile r]
    set input_data [read $fp]
    close $fp
    
    set result [parse_tree_to_dot $input_data]
    
    set out_fp [open $outfile w]
    puts -nonewline $out_fp $result
    close $out_fp
    
    puts "Source: $infile"
    puts "Result: $outfile"
} else {
    puts "Usage: ./script.tcl <input_file> \[-o output_file\]"
}


Monday, February 09, 2026

Search Source Files for Matching Key Phrases

 
##
# search all sources for pattern string
# returns a tagged list, those matching, and those not-matching
##
proc checkAllSources { {pattern USE_DOC_VAR} } {

    package require fileutil
    
    set FILES [fileutil::findByPattern ../src *.c] 
    
    foreach w [gnocl::widgets] {
        foreach f $FILES { 
            if { [string first /$w.c $f] != -1 } { 
                set fp [open $f r]
                set DATA [read $fp]
                close $fp

                if { [string first $pattern $DATA] != -1 } { 
                    lappend yes $f 
                } else {
                    lappend no $f
                    
                }
            }     
        }
    }
    return "yes {$yes} no {$no}"
}

puts [join [dict get [checkAllSources gnoclGetDocumentationVarArgs] yes] \n]

Sunday, January 25, 2026

Using a Simple C While-Loop to Step Throught Option Arrays

Sometimes its necessary to run through a GnoclOption array. As such arrays are always NULL terminated, a simple while loop solves the problem.


gint idx = 0; 

while ( tagOptions[idx++].optName != NULL )  {

g_print ( "*%d %s\n", idx, tagOptions[idx].optName );

if ( strcmp ( tagOptions[idx].optName, "-myOption" ) ) == 0 ) {

GNOCL_DEBUG_MSG ( "got it!" )

break;

 }

 

As can be seem in the above example, a GnoclOption array is an array of structures. 

 

If just the array size is needed, then simply use the macro GNOCL_TOTAL_OPTIONS(option) to return the length, less the NULL, of course.

The macro ARRAY_SIZE(x) will provide the overall size of an array, including any NULL termination. 

 

 

Monday, October 06, 2025

Convert Serialized GtkTextBuffer contents to ASCII

 The contents of a GtkTextBuffer can be saved as serialized binary file. Whilst converting such a file to ascii may be problematic if the buffer contains binary image data, saving text can be achieved quite simply as follows:

 

$txt save myfile.ser 

set file [open "myfile.ser" rb]
set DATA [read $file]
close $file

set fp [open myfile.txt w]
puts $header
set i 0
foreach line [split $DATA \n] {
    if {$i == 0} {
        puts $fp ":GTKTEXTBUFFERCONTENTS-0001 <text_view_markup>"
        set i 1
        continue}
    puts $fp $line
}
close $fp

 

Monday, September 29, 2025

Creating Tagged Lists From Separate Key - Value Lists

Various Tcl and Gnocl operations will return a list of values. At times, however, it is useful to preserve the lists of values as a tagged list; in other words, as a dict entry. Assuming that the returned value names are keys, the Tcl format command command can be used to complete the operation. For example:

 

set main [gnocl::window -setSize 0.25]

puts [format "x %s y %s width %s height %s" {*}[$main geometry]] 

 

gives:

x 0 y 0 width 480 height 270

Alternatively, a proc can be written using the foreach command:

 

proc tagList {tagnames values} {
    foreach tag $tagnames val $values {
        lappend buff $tag $val
    
    return $buff
}    

Sunday, June 15, 2025

Parsing Unstructed Option Value String

In using a Tcl script as a command line call, being able to parse the argument passed to the script is vital. The following snippet shows how to retrieve and assign such values.

line 2

line 3

 

# list of tested options
set opts "-a -b -c -d -e"

##
# parse a string of arguments
##
# arguments:
#    opts    - list of options not in dict format
#    args    - list of options-values to be parsed
# returns:
#    tagged list of option/values pairs as dict
#
proc parse {opts args} {

    set i 0
    foreach item $args {
        if { $item in $opts } { 
            set tmp [string trim $item -]
            incr i
        } else {
            lappend opt($tmp) $item
        }        
    }

    if { $i != [array size opt] } { 
        error -errorinfo "Error: unbalanced list in arguments."
    }

    return [array get opt]
}

The following line will result in an error as no value passed for option -e. The list element -5 will not be mistaken for an option as it is not a member of the valid options list.

parse $opts -a {1 2 3} -b a b c -c 4 -3 -d -5 -e 

In the above example option -a receives a braced list, that is its contents is a single member of a list. The following line is balanced:

parse $opts -a {1 2 3} -b a b c -c 4 -3 -d -5 -e f

After stripping initial dashes, the following tagged list is returned 

d -5 e f a {{1 2 3}} b {a b c} c {4 -3}

The unsorted ordering being the result of maintaining values in the parse procedure as an array, In practice, this will have no impact of the application of the code. 

Comparing Version Numbers

Sometimes it necessary to compare a current version number against a minimum requirement. This simple wrap around the package command shows how to do it. 

VERSION NUMBERS

Version numbers consist of one or more decimal numbers separated by dots, such as 2 or 1.162 or 3.1.13.1. The first number is called the major version number. Larger numbers correspond to later versions of a package, with leftmost numbers having greater significance. For example, version 2.1 is later than 1.3 and version 3.4.6 is later than 3.3.5. Missing fields are equivalent to zeroes: version 1.3 is the same as version 1.3.0 and 1.3.0.0, so it is earlier than 1.3.1 or 1.3.0.2. A later version number is assumed to be upwards compatible with an earlier version number as long as both versions have the same major version number. For example, Tcl scripts written for version 2.3 of a package should work unchanged under versions 2.3.2, 2.4, and 2.5.1. Changes in the major version number signify incompatible changes: if code is written to use version 2.1 of a package, it is not guaranteed to work unmodified with either version 1.7.3 or version 3.1.


 

# https://wiki.tcl-lang.org/page/package+vsatisfies
proc version {ver min} {

    package vcompare $ver $min
    #package vsatisfies $ver $min

}

puts [version 3.22.1 3.23.3] ;# -1 (earlier)
puts [version 3.24.1 3.23.3] ;#  1 (later)
puts [version 3.23.3 3.23.3] ;#  0 (equal)
puts [version 4.33.5 3.23.3] ;#  1 (later)
puts [version 4.23.5 4.34.3] ;# -1 (earlier)