Sunday, June 28, 2026

Migrating from Tck 8.6 to 9+

Legacy Tcl & Gnocl Package Loading Fixes

Problem

The default interpreter on your system is Tcl 9.0 (/usr/local/bin/tclsh). However, the Gnocl package is only compiled for Tcl 8.x. When running scripts, if they are executed directly using tclsh script.tcl (or if your shell uses the default tclsh), they will run under Tcl 9.0 and fail to load Gnocl.

 

Additionally, shell shebang restart blocks (# !/bin/sh ... exec tclsh) only restart under Tcl 8.6 if executed as ./script.tcl from the shell. If you execute them directly as tclsh script.tcl, Tcl treats the shell shebang as a comment, bypasses it, and runs with Tcl 9.0, leading to the same package load error.

Solutions Implemented

  1. Interpreter Redirection Changed the shell restart blocks to explicitly point to /usr/bin/tclsh8.6

  2. Robust Runtime Version Check Added a runtime Tcl version check inside  and . If either file is loaded or run directly by an incompatible Tcl version (like Tcl 9.0), it will immediately restart itself under tclsh8.6 with full stdout and stderr redirection:

    tcl
    if {[info tclversion] ne "8.6"} {
    exec tclsh8.6 $argv0 {*}$argv >@ stdout 2>@ stderr
    exit
    }


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. 

 

 

Migrating from Tck 8.6 to 9+