← Back to all projects

LEARN TCL TK

Learn Tcl/Tk: From Zero to Scripting Master

Goal: Deeply understand Tcl/Tk—from its unique “everything is a string” philosophy through GUI programming with Tk, automation with Expect, and embedding in C applications. Master a language that’s deceptively simple yet remarkably powerful.


Why Tcl/Tk Matters

Tcl (Tool Command Language) is unlike any language you’ve used before. Created by John Ousterhout in 1988, it has a radically simple syntax: everything is a command, and everything is a string. This simplicity enables:

  • Rapid GUI development: Tk makes creating GUIs trivially easy
  • Embedded scripting: Easily add scripting to C/C++ applications
  • Automation: Expect automates interactive programs (SSH, telnet, etc.)
  • EDA tools: Dominant in chip design (Synopsys, Cadence, Xilinx/AMD Vivado)
  • Testing: Great for test harnesses and automation frameworks

After completing these projects, you will:

  • Think in Tcl’s unique paradigm (commands and substitution)
  • Build cross-platform GUIs with Tk
  • Automate system administration with Expect
  • Embed Tcl in your own applications
  • Understand why Tcl is still thriving after 35+ years

Core Concept Analysis

The Tcl Philosophy

Tcl has just 12 syntax rules. The entire language fits on one page:

# Rule 1: A script is a sequence of commands separated by newlines or semicolons
puts "Hello"; puts "World"

# Rule 2: A command is a list of words separated by whitespace
set x 42

# Rule 3: The first word is the command name, rest are arguments
expr 5 + 3

# Rule 4: Words are separated by whitespace
set name "John Doe"

# Rule 5: Double quotes allow substitutions but preserve whitespace
puts "The value is $x"

# Rule 6: Braces prevent substitutions
puts {The value is $x}  ;# Prints literally: The value is $x

# Rule 7: Square brackets substitute command results
set len [string length "Hello"]

# Rule 8: Dollar sign substitutes variable values
puts "x = $x"

# Rule 9: Backslash escapes special characters
puts "She said \"Hello\""

# Rule 10: Comments start with #
# This is a comment

# Rule 11: Everything is a string (including numbers!)
set num 42      ;# "42" is a string that looks like a number
expr $num + 8   ;# Tcl interprets it as a number here

# Rule 12: Commands return string results
set result [expr 5 + 3]  ;# result is "8" (a string)

Key Tcl Concepts

  1. Everything is a String
    set x 42         ;# x contains the string "42"
    set y {a b c}    ;# y contains the string "a b c"
    expr $x + 8      ;# Tcl interprets "42" as a number when needed
    
  2. Command Substitution (square brackets)
    set now [clock seconds]           ;# Evaluate clock seconds, use result
    set upper [string toupper hello]  ;# Result: "HELLO"
    
  3. Variable Substitution (dollar sign)
    set name "World"
    puts "Hello, $name!"  ;# Hello, World!
    
  4. Braces vs Quotes
    set x 10
    puts "x is $x"    ;# x is 10 (substitution happens)
    puts {x is $x}    ;# x is $x (literal, no substitution)
    
  5. Lists (Tcl’s fundamental data structure)
    set colors {red green blue}
    lindex $colors 1           ;# green
    lappend colors yellow      ;# red green blue yellow
    llength $colors            ;# 4
    
  6. Arrays (associative arrays / hash maps)
    set person(name) "Alice"
    set person(age) 30
    array names person         ;# name age
    
  7. Dictionaries (ordered key-value pairs)
    set person [dict create name "Bob" age 25]
    dict get $person name      ;# Bob
    dict set person city "NYC"
    

Tk Widget Hierarchy

Tk Widget Classes
│
├── Toplevel Widgets
│   ├── . (root window)
│   └── toplevel
│
├── Basic Widgets
│   ├── label, message
│   ├── button, checkbutton, radiobutton
│   ├── entry, spinbox
│   ├── text, listbox
│   └── scale, scrollbar
│
├── Container Widgets
│   ├── frame, labelframe
│   ├── panedwindow
│   └── notebook (ttk)
│
├── Menu Widgets
│   ├── menu, menubutton
│   └── tk_popup
│
└── Canvas & Special
    ├── canvas
    └── treeview (ttk)

Layout Managers

Tk provides three geometry managers:

  1. pack: Stack widgets in order
  2. grid: Place widgets in rows/columns
  3. place: Absolute positioning (rarely used)

Project List

Projects progress from Tcl fundamentals through advanced Tk GUIs and system automation.


Project 1: Interactive Calculator and REPL Explorer

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl
  • Alternative Programming Languages: Python, Ruby
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Tcl Basics / REPL
  • Software or Tool: Calculator
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapters 1-3)

What you’ll build: An interactive calculator that demonstrates Tcl’s expr command, variable handling, and the power of the Tcl REPL (tclsh).

Why it teaches Tcl fundamentals: Tcl’s interactive shell is one of its greatest learning tools. By building a calculator, you’ll internalize command syntax, substitution rules, and how “everything is a string” actually works.

Core challenges you’ll face:

  • Understanding substitution → maps to when $ and [] are evaluated
  • expr syntax → maps to mathematical expressions in Tcl
  • Procedures → maps to defining reusable commands
  • Error handling → maps to catch and try/throw

Key Concepts:

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic programming concepts. No prior Tcl experience needed.

Real world outcome:

$ tclsh calculator.tcl

╔════════════════════════════════════╗
║     Tcl Interactive Calculator      ║
╠════════════════════════════════════╣
║  Commands:                          ║
║    calc <expression>  - evaluate    ║
║    set <var> <value>  - store       ║
║    vars               - list vars   ║
║    history            - show history║
║    quit               - exit        ║
╚════════════════════════════════════╝

calc> calc 5 + 3 * 2
Result: 11

calc> set radius 10
radius = 10

calc> calc 3.14159 * $radius ** 2
Result: 314.159

calc> set area [calc 3.14159 * $radius ** 2]
area = 314.159

calc> calc sin(0.5) + cos(0.5)
Result: 1.357008

calc> vars
  radius = 10
  area = 314.159

calc> history
  1: 5 + 3 * 2 = 11
  2: 3.14159 * $radius ** 2 = 314.159
  3: sin(0.5) + cos(0.5) = 1.357008

Implementation Hints:

Basic Tcl syntax to learn:

# Variables
set x 10
set y 20
puts "x + y = [expr {$x + $y}]"

# Always brace expressions! (for performance and safety)
expr {$x + $y}      ;# Good: compiled, safe
expr $x + $y        ;# Bad: double substitution, slow

# Procedures
proc add {a b} {
    return [expr {$a + $b}]
}
puts [add 5 3]  ;# 8

# Control flow
if {$x > 5} {
    puts "x is large"
} elseif {$x > 0} {
    puts "x is positive"
} else {
    puts "x is not positive"
}

# Loops
for {set i 0} {$i < 10} {incr i} {
    puts "i = $i"
}

foreach item {apple banana cherry} {
    puts $item
}

while {$x > 0} {
    puts $x
    incr x -1
}

# Error handling
if {[catch {expr {1/0}} result]} {
    puts "Error: $result"
}

# Modern error handling (Tcl 8.6+)
try {
    expr {1/0}
} on error {msg} {
    puts "Caught error: $msg"
}

Calculator structure:

#!/usr/bin/env tclsh

# Calculator state
set ::history {}
set ::vars [dict create]

proc calc {expression} {
    # Substitute variables from our vars dict
    set expr_sub $expression
    dict for {name value} $::vars {
        regsub -all "\\$$name\\b" $expr_sub $value expr_sub
    }

    # Evaluate
    if {[catch {expr $expr_sub} result]} {
        puts "Error: $result"
        return
    }

    # Store in history
    lappend ::history [list $expression $result]

    return $result
}

proc set_var {name value} {
    dict set ::vars $name $value
    puts "$name = $value"
}

proc show_vars {} {
    dict for {name value} $::vars {
        puts "  $name = $value"
    }
}

proc show_history {} {
    set n 1
    foreach entry $::history {
        lassign $entry expr result
        puts "  $n: $expr = $result"
        incr n
    }
}

# Main REPL loop
proc repl {} {
    while {1} {
        puts -nonewline "calc> "
        flush stdout
        gets stdin line

        if {$line eq "quit"} break

        set cmd [lindex $line 0]
        switch $cmd {
            calc    { puts "Result: [calc [lrange $line 1 end]]" }
            set     { set_var [lindex $line 1] [lindex $line 2] }
            vars    { show_vars }
            history { show_history }
            default { puts "Unknown command: $cmd" }
        }
    }
}

repl

Learning milestones:

  1. You can use tclsh interactively → You understand the REPL
  2. Variables and substitution work → You understand $ and []
  3. Procedures are defined → You understand proc
  4. Error handling works → You understand catch/try

Project 2: File Manager and Text Processor

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl
  • Alternative Programming Languages: Python, Perl
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: File I/O / String Processing
  • Software or Tool: File Utilities
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapters 4-5, 9)

What you’ll build: A suite of file utilities: a grep clone, a find clone, a batch renamer, and a log analyzer—all demonstrating Tcl’s powerful string and file handling.

Why it teaches file/string processing: Tcl excels at text processing. Its regexp, string, and file commands are elegant and powerful. Building these utilities shows why Tcl is still used for scripting.

Core challenges you’ll face:

  • File operations → maps to open, read, write, close, glob
  • String manipulation → maps to string command family
  • Regular expressions → maps to regexp and regsub
  • List processing → maps to foreach, lmap, lsort

Key Concepts:

  • File I/O: “Practical Programming in Tcl and Tk” Chapter 9 - Welch
  • String Commands: Tcl Wiki - string
  • Regular Expressions: “Practical Programming in Tcl and Tk” Chapter 11 - Welch

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Project 1 completed.

Real world outcome:

$ tclsh tgrep.tcl "proc.*{" *.tcl

calculator.tcl:15: proc add {a b} {
calculator.tcl:19: proc calc {expression} {
fileutils.tcl:8: proc tgrep {pattern files} {
fileutils.tcl:42: proc tfind {dir args} {

Found 4 matches in 2 files

$ tclsh tfind.tcl . -name "*.tcl" -size +1k

./calculator.tcl (2.3 KB)
./fileutils.tcl (4.1 KB)
./gui_demo.tcl (8.7 KB)

Found 3 files

$ tclsh rename.tcl "*.jpg" "photo_{n:03}.jpg" --preview

Preview mode (no files changed):
  IMG_001.jpg → photo_001.jpg
  IMG_002.jpg → photo_002.jpg
  vacation_pic.jpg → photo_003.jpg

$ tclsh loganalyzer.tcl access.log --top 10

Top 10 IP Addresses:
  1. 192.168.1.100    2,847 requests
  2. 10.0.0.52        1,923 requests
  3. 172.16.0.15      1,456 requests
  ...

Status Code Distribution:
  200 OK:          78.3%
  304 Not Modified: 12.1%
  404 Not Found:    5.2%
  500 Error:        0.8%

Implementation Hints:

File operations:

# Reading a file
set fh [open "myfile.txt" r]
set content [read $fh]
close $fh

# Or in one line
set content [read [open "myfile.txt"]]

# Line by line reading
set fh [open "myfile.txt" r]
while {[gets $fh line] >= 0} {
    puts "Line: $line"
}
close $fh

# Writing a file
set fh [open "output.txt" w]
puts $fh "Hello, World!"
close $fh

# File info
file exists myfile.txt    ;# 1 or 0
file size myfile.txt      ;# size in bytes
file mtime myfile.txt     ;# modification time (seconds)
file dirname /path/to/file.txt  ;# /path/to
file tail /path/to/file.txt     ;# file.txt
file extension file.txt         ;# .txt

# Glob (file pattern matching)
set tcl_files [glob *.tcl]
set all_files [glob -nocomplain -directory /tmp *]

String manipulation:

# String operations
string length "Hello"           ;# 5
string index "Hello" 0          ;# H
string range "Hello" 1 3        ;# ell
string toupper "hello"          ;# HELLO
string tolower "HELLO"          ;# hello
string trim "  hello  "         ;# hello
string first "l" "Hello"        ;# 2
string last "l" "Hello"         ;# 3
string replace "Hello" 0 0 "J"  ;# Jello
string repeat "ab" 3            ;# ababab

# String comparison
string equal "abc" "ABC"              ;# 0 (false)
string equal -nocase "abc" "ABC"      ;# 1 (true)
string compare "abc" "def"            ;# -1 (abc < def)

# String matching (glob-style)
string match "*.tcl" "hello.tcl"      ;# 1

# split and join
split "a,b,c" ","                     ;# {a b c}
join {a b c} ","                      ;# a,b,c

Regular expressions:

# Basic matching
regexp {^\d+$} "12345"                ;# 1 (matches)
regexp {^\d+$} "12abc"                ;# 0 (no match)

# Capturing groups
set line "Name: John, Age: 30"
if {[regexp {Name: (\w+), Age: (\d+)} $line match name age]} {
    puts "Name: $name, Age: $age"
}

# All matches
set text "cat sat on a mat"
set matches [regexp -all -inline {[a-z]at} $text]
;# {cat sat mat}

# Substitution
set text "Hello World"
regsub {World} $text "Tcl" result
puts $result  ;# Hello Tcl

# Global substitution
regsub -all {[aeiou]} "hello" "_" result
puts $result  ;# h_ll_

tgrep implementation:

proc tgrep {pattern files} {
    set matches 0
    set files_with_matches 0

    foreach file [glob -nocomplain {*}$files] {
        set fh [open $file r]
        set line_num 0
        set file_matched 0

        while {[gets $fh line] >= 0} {
            incr line_num
            if {[regexp $pattern $line]} {
                if {!$file_matched} {
                    incr files_with_matches
                    set file_matched 1
                }
                puts "$file:$line_num: $line"
                incr matches
            }
        }
        close $fh
    }

    puts "\nFound $matches matches in $files_with_matches files"
}

# Usage
if {$argc >= 2} {
    tgrep [lindex $argv 0] [lrange $argv 1 end]
} else {
    puts "Usage: tgrep pattern files..."
}

Learning milestones:

  1. File read/write works → You understand file I/O
  2. String commands are natural → You understand text processing
  3. Regexp captures groups → You understand regular expressions
  4. Tools are useful daily → You’ve built practical utilities

Project 3: First Tk GUI - Temperature Converter

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python (tkinter)
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Tk Basics / Widget Hierarchy
  • Software or Tool: GUI Application
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapter 19)

What you’ll build: A temperature converter GUI with entries for Celsius and Fahrenheit that update each other in real-time, introducing Tk widgets, layout, and variable traces.

Why it teaches Tk fundamentals: This simple app demonstrates the core Tk pattern: create widgets, configure them, lay them out, and respond to events. Variable traces show Tk’s unique reactive programming model.

Core challenges you’ll face:

  • Widget creation → maps to creating and naming widgets
  • Layout with pack/grid → maps to geometry management
  • Variable binding → maps to -textvariable and traces
  • Event handling → maps to bindings and callbacks

Key Concepts:

  • Tk Basics: ZetCode Tcl/Tk Tutorial
  • Widget Options: “Practical Programming in Tcl and Tk” Chapter 19 - Welch
  • Variable Traces: “Practical Programming in Tcl and Tk” Chapter 4.5 - Welch

Difficulty: Beginner Time estimate: Weekend Prerequisites: Project 1 completed.

Real world outcome:

┌────────────────────────────────────────┐
│        Temperature Converter            │
├────────────────────────────────────────┤
│                                        │
│   Celsius:    [  37.0     ] °C        │
│                                        │
│   Fahrenheit: [  98.6     ] °F        │
│                                        │
│       [ Convert C→F ]  [ Convert F→C ] │
│                                        │
│   ──────────────────────────────       │
│   Quick Reference:                     │
│   • Water freezes: 0°C / 32°F         │
│   • Body temp: 37°C / 98.6°F          │
│   • Water boils: 100°C / 212°F        │
│                                        │
└────────────────────────────────────────┘

Implementation Hints:

Basic Tk structure:

#!/usr/bin/env wish

# Create the main window (. is the root)
wm title . "Temperature Converter"
wm geometry . 300x200

# Create widgets (widget_type path ?options?)
label .title -text "Temperature Converter" -font {Helvetica 16 bold}
label .celsius_label -text "Celsius:"
entry .celsius_entry -textvariable celsius -width 10
label .fahrenheit_label -text "Fahrenheit:"
entry .fahrenheit_entry -textvariable fahrenheit -width 10
button .convert -text "Convert C→F" -command convert_c_to_f

# Layout with grid
grid .title -row 0 -column 0 -columnspan 2 -pady 10
grid .celsius_label -row 1 -column 0 -sticky e
grid .celsius_entry -row 1 -column 1 -sticky w
grid .fahrenheit_label -row 2 -column 0 -sticky e
grid .fahrenheit_entry -row 2 -column 1 -sticky w
grid .convert -row 3 -column 0 -columnspan 2 -pady 10

# Conversion procedure
proc convert_c_to_f {} {
    global celsius fahrenheit
    if {[string is double -strict $celsius]} {
        set fahrenheit [format "%.1f" [expr {$celsius * 9.0/5.0 + 32}]]
    }
}

# Start the event loop (optional with wish)
# tkwait window .

Variable traces for auto-conversion:

# Instead of button, use traces for real-time conversion
trace add variable celsius write on_celsius_change
trace add variable fahrenheit write on_fahrenheit_change

set updating 0  ;# Prevent infinite loop

proc on_celsius_change {args} {
    global celsius fahrenheit updating
    if {$updating} return
    if {![string is double -strict $celsius] || $celsius eq ""} return

    set updating 1
    set fahrenheit [format "%.1f" [expr {$celsius * 9.0/5.0 + 32}]]
    set updating 0
}

proc on_fahrenheit_change {args} {
    global celsius fahrenheit updating
    if {$updating} return
    if {![string is double -strict $fahrenheit] || $fahrenheit eq ""} return

    set updating 1
    set celsius [format "%.1f" [expr {($fahrenheit - 32) * 5.0/9.0}]]
    set updating 0
}

Widget options explained:

# Common widget options
label .l -text "Label"           \
         -font {Arial 12 bold}   \
         -fg blue                \
         -bg white               \
         -width 20               \
         -anchor w               \
         -relief sunken          \
         -borderwidth 2

entry .e -textvariable myvar     \
         -width 30               \
         -show "*"               \  ;# For passwords
         -state readonly         \
         -validate key           \
         -validatecommand {string is double %P}

button .b -text "Click Me"       \
          -command {puts "Clicked!"} \
          -width 15              \
          -state normal          \  ;# or disabled
          -relief raised

Layout managers:

# Pack - simple stacking
pack .widget -side top -fill x -expand 1 -padx 5 -pady 5

# Grid - rows and columns
grid .widget -row 0 -column 0 -rowspan 2 -columnspan 3 -sticky nsew

# Configure grid weights for resizing
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1

Learning milestones:

  1. Window appears with widgets → You understand Tk basics
  2. Layout is correct → You understand geometry managers
  3. Conversion works → You understand commands and variables
  4. Real-time updates work → You understand traces

Project 4: Todo List Application

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python (tkinter)
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Listbox / File Persistence
  • Software or Tool: Todo App
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapters 21-22)

What you’ll build: A complete todo list application with add/delete/edit, priority levels, completion marking, and file persistence—a practical app demonstrating listbox, menus, and data management.

Why it teaches Tk widgets: The listbox is Tk’s workhorse for displaying collections. Combined with entries, buttons, and menus, you’ll learn the widget interactions that make real applications.

Core challenges you’ll face:

  • Listbox operations → maps to insert, delete, selection
  • Menu creation → maps to menubar and popup menus
  • Data persistence → maps to saving/loading from files
  • Keyboard shortcuts → maps to bindings and accelerators

Key Concepts:

  • Listbox Widget: “Practical Programming in Tcl and Tk” Chapter 21 - Welch
  • Menus: “Practical Programming in Tcl and Tk” Chapter 24 - Welch
  • Event Bindings: “Practical Programming in Tcl and Tk” Chapter 20 - Welch

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Project 3 completed.

Real world outcome:

┌─────────────────────────────────────────────────────┐
│ File  Edit  View  Help                               │
├─────────────────────────────────────────────────────┤
│                                                      │
│  [ New Task                              ] [+ Add]  │
│                                                      │
│  ┌─────────────────────────────────────────────┐   │
│  │ ☐ [!] Buy groceries                         │   │
│  │ ☑ [ ] Finish Tcl tutorial                   │   │
│  │ ☐ [!] Call dentist                          │   │
│  │ ☐ [ ] Read Chapter 5                        │   │
│  │ ☐ [!] Submit report                         │   │
│  └─────────────────────────────────────────────┘   │
│                                                      │
│  [✓ Complete]  [Edit]  [Delete]  [↑ Up]  [↓ Down]  │
│                                                      │
│  ─────────────────────────────────────────────      │
│  Total: 5 | Completed: 1 | Pending: 4               │
└─────────────────────────────────────────────────────┘

Implementation Hints:

Listbox operations:

# Create listbox with scrollbar
frame .list_frame
listbox .list_frame.lb -yscrollcommand {.list_frame.sb set} \
                       -selectmode single \
                       -height 10 \
                       -width 50
scrollbar .list_frame.sb -command {.list_frame.lb yview}

pack .list_frame.lb -side left -fill both -expand 1
pack .list_frame.sb -side right -fill y

# Insert items
.list_frame.lb insert end "Task 1"
.list_frame.lb insert end "Task 2"
.list_frame.lb insert 0 "Task at top"  ;# Insert at position

# Get selection
set selected [.list_frame.lb curselection]
if {$selected ne ""} {
    set item [.list_frame.lb get $selected]
    puts "Selected: $item"
}

# Delete selection
.list_frame.lb delete $selected

# Modify item
.list_frame.lb delete $selected
.list_frame.lb insert $selected "Modified item"
.list_frame.lb selection set $selected

# Get all items
set all_items [.list_frame.lb get 0 end]

Menu system:

# Create menubar
menu .menubar
. configure -menu .menubar

# File menu
menu .menubar.file -tearoff 0
.menubar add cascade -label "File" -menu .menubar.file
.menubar.file add command -label "New" -command new_file -accelerator "Ctrl+N"
.menubar.file add command -label "Open..." -command open_file -accelerator "Ctrl+O"
.menubar.file add command -label "Save" -command save_file -accelerator "Ctrl+S"
.menubar.file add separator
.menubar.file add command -label "Exit" -command exit -accelerator "Ctrl+Q"

# Edit menu
menu .menubar.edit -tearoff 0
.menubar add cascade -label "Edit" -menu .menubar.edit
.menubar.edit add command -label "Delete" -command delete_item -accelerator "Delete"
.menubar.edit add command -label "Mark Complete" -command toggle_complete -accelerator "Space"

# Keyboard shortcuts
bind . <Control-n> new_file
bind . <Control-o> open_file
bind . <Control-s> save_file
bind . <Control-q> exit
bind .list_frame.lb <Delete> delete_item
bind .list_frame.lb <space> toggle_complete
bind .list_frame.lb <Double-1> edit_item

Data model with persistence:

# Task structure: dict with id, text, completed, priority
set tasks {}
set next_id 0

proc add_task {text {priority 0}} {
    global tasks next_id
    set task [dict create \
        id $next_id \
        text $text \
        completed 0 \
        priority $priority \
        created [clock seconds]]
    lappend tasks $task
    incr next_id
    refresh_list
    save_tasks
}

proc toggle_complete {idx} {
    global tasks
    set task [lindex $tasks $idx]
    dict set task completed [expr {![dict get $task completed]}]
    lset tasks $idx $task
    refresh_list
    save_tasks
}

proc save_tasks {{filename "todo.dat"}} {
    global tasks
    set fh [open $filename w]
    puts $fh $tasks
    close $fh
}

proc load_tasks {{filename "todo.dat"}} {
    global tasks next_id
    if {[file exists $filename]} {
        set fh [open $filename r]
        set tasks [read $fh]
        close $fh
        # Find max id
        set next_id 0
        foreach task $tasks {
            set id [dict get $task id]
            if {$id >= $next_id} {
                set next_id [expr {$id + 1}]
            }
        }
        refresh_list
    }
}

proc refresh_list {} {
    global tasks
    .list_frame.lb delete 0 end
    foreach task $tasks {
        set marker [expr {[dict get $task completed] ? "☑" : "☐"}]
        set priority [expr {[dict get $task priority] ? "!" : " "}]
        set text [dict get $task text]
        .list_frame.lb insert end "$marker \[$priority\] $text"
    }
    update_status
}

Learning milestones:

  1. Listbox displays items → You understand listbox basics
  2. Add/delete works → You understand listbox manipulation
  3. Menu with shortcuts works → You understand menus and bindings
  4. Persistence works → You understand file-based data storage

Project 5: Drawing Application with Canvas

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python (tkinter)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Canvas Widget / Graphics
  • Software or Tool: Drawing Application
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapter 26)

What you’ll build: A vector drawing application with tools for lines, rectangles, ovals, and freehand drawing, supporting colors, undo/redo, and export to PostScript/PNG.

Why it teaches Canvas: The canvas widget is Tk’s powerhouse for graphics. It can display shapes, images, text, and even embedded widgets. Understanding canvas unlocks games, visualizations, and custom widgets.

Core challenges you’ll face:

  • Canvas items → maps to creating and manipulating shapes
  • Mouse events → maps to tracking for drawing
  • Item tags → maps to grouping and manipulating items
  • Coordinate systems → maps to scrolling and zooming

Resources for key challenges:

Key Concepts:

  • Canvas Widget: “Practical Programming in Tcl and Tk” Chapter 26 - Welch
  • Canvas Items: Tcl Wiki - canvas
  • Event Coordinates: Canvas coordinate system

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Project 4 completed.

Real world outcome:

┌─────────────────────────────────────────────────────────────┐
│ File  Edit  Tools  Colors  Help                              │
├──────┬──────────────────────────────────────────────────────┤
│      │                                                       │
│ [→]  │                                                       │
│ [□]  │            ┌─────────────────┐                       │
│ [○]  │            │                 │                       │
│ [/]  │            │    Drawing      │     ●                 │
│ [~]  │            │    Area         │    /│\                │
│ [T]  │            │                 │   / │ \               │
│ [⌫]  │            └─────────────────┘                       │
│      │                     ~~~~~~~~~~~~~~~~~~~               │
│ ──── │                                                       │
│ [▣]  │                                                       │
│ [▤]  │                                                       │
│ [▥]  │                                                       │
├──────┴──────────────────────────────────────────────────────┤
│ Tool: Rectangle │ Color: Blue │ Objects: 12 │ Zoom: 100%     │
└─────────────────────────────────────────────────────────────┘

Implementation Hints:

Canvas basics:

# Create canvas with scrollbars
canvas .c -width 800 -height 600 \
          -bg white \
          -scrollregion {0 0 2000 2000} \
          -xscrollcommand {.hscroll set} \
          -yscrollcommand {.vscroll set}

scrollbar .hscroll -orient horizontal -command {.c xview}
scrollbar .vscroll -orient vertical -command {.c yview}

grid .c -row 0 -column 0 -sticky nsew
grid .vscroll -row 0 -column 1 -sticky ns
grid .hscroll -row 1 -column 0 -sticky ew
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1

Canvas items:

# Create items (returns item id)
set line_id [.c create line 10 10 100 100 -fill red -width 2]
set rect_id [.c create rectangle 50 50 150 100 -outline blue -fill yellow]
set oval_id [.c create oval 200 200 300 280 -outline green -width 3]
set text_id [.c create text 150 150 -text "Hello" -font {Arial 24}]
set poly_id [.c create polygon 10 10 50 50 10 50 -fill purple]
set arc_id [.c create arc 100 100 200 200 -start 0 -extent 90 -style arc]

# Item with tags
.c create rectangle 10 10 50 50 -tags {shape movable rect1}

# Manipulate items
.c coords $rect_id 60 60 160 110    ;# Move/resize
.c itemconfigure $rect_id -fill red  ;# Change properties
.c move $rect_id 10 10               ;# Relative move
.c delete $rect_id                   ;# Remove
.c raise $line_id                    ;# Bring to front
.c lower $line_id                    ;# Send to back

# Find items
set all_rects [.c find withtag rect]
set at_point [.c find overlapping 50 50 51 51]
set in_area [.c find enclosed 0 0 100 100]

Drawing tool implementation:

set current_tool "rectangle"
set current_color "black"
set fill_color ""
set line_width 2
set start_x 0
set start_y 0
set current_item ""
set history {}
set history_idx -1

# Mouse bindings for drawing
.c bind all <Button-1> {start_action %x %y}
.c bind all <B1-Motion> {continue_action %x %y}
.c bind all <ButtonRelease-1> {end_action %x %y}

proc start_action {x y} {
    global current_tool start_x start_y current_item
    set start_x [.c canvasx $x]
    set start_y [.c canvasy $y]

    switch $current_tool {
        select {
            # Find item under cursor
            set item [.c find closest $start_x $start_y]
            if {$item ne ""} {
                .c addtag selected withtag $item
                .c itemconfigure selected -outline red
            }
        }
        rectangle - oval - line {
            # Start drawing shape
            set current_item [create_shape $start_x $start_y $start_x $start_y]
        }
        pencil {
            # Start freehand line
            set current_item [.c create line $start_x $start_y $start_x $start_y \
                              -fill $::current_color -width $::line_width \
                              -tags drawing]
        }
    }
}

proc continue_action {x y} {
    global current_tool start_x start_y current_item
    set x [.c canvasx $x]
    set y [.c canvasy $y]

    switch $current_tool {
        select {
            # Move selected items
            .c move selected [expr {$x - $start_x}] [expr {$y - $start_y}]
            set start_x $x
            set start_y $y
        }
        rectangle - oval - line {
            # Resize shape
            update_shape $current_item $start_x $start_y $x $y
        }
        pencil {
            # Extend freehand line
            lappend coords $x $y
            .c coords $current_item [concat [.c coords $current_item] $x $y]
        }
    }
}

proc end_action {x y} {
    global current_item history
    if {$current_item ne ""} {
        lappend history [list create $current_item [.c coords $current_item]]
    }
    set current_item ""
}

proc create_shape {x1 y1 x2 y2} {
    global current_tool current_color fill_color line_width
    switch $current_tool {
        rectangle {
            return [.c create rectangle $x1 $y1 $x2 $y2 \
                    -outline $current_color -fill $fill_color \
                    -width $line_width -tags drawing]
        }
        oval {
            return [.c create oval $x1 $y1 $x2 $y2 \
                    -outline $current_color -fill $fill_color \
                    -width $line_width -tags drawing]
        }
        line {
            return [.c create line $x1 $y1 $x2 $y2 \
                    -fill $current_color -width $line_width \
                    -tags drawing]
        }
    }
}

Undo/redo:

proc undo {} {
    global history history_idx
    if {$history_idx >= 0} {
        set action [lindex $history $history_idx]
        lassign $action type item_id
        .c delete $item_id
        incr history_idx -1
    }
}

proc redo {} {
    global history history_idx
    if {$history_idx < [llength $history] - 1} {
        incr history_idx
        set action [lindex $history $history_idx]
        lassign $action type item_id coords
        # Recreate the item...
    }
}

Export:

proc export_postscript {filename} {
    .c postscript -file $filename
    puts "Exported to $filename"
}

proc export_png {filename} {
    # Requires Img package
    package require Img
    set img [image create photo -format window -data .c]
    $img write $filename -format png
}

Learning milestones:

  1. Shapes appear on canvas → You understand canvas items
  2. Drawing with mouse works → You understand canvas events
  3. Selection and move work → You understand item manipulation
  4. Undo/redo works → You understand action history

Project 6: Text Editor with Syntax Highlighting

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python (tkinter)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Text Widget / Search/Replace
  • Software or Tool: Text Editor
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapter 23)

What you’ll build: A programmer’s text editor with syntax highlighting for Tcl, line numbers, find/replace, multiple tabs, and file operations—a practical application of Tk’s powerful text widget.

Why it teaches the text widget: The text widget is surprisingly capable—it supports tagged regions, embedded images/widgets, and complex formatting. Building an editor teaches you to think in terms of indices and marks.

Core challenges you’ll face:

  • Text indices → maps to line.char notation
  • Tags for highlighting → maps to styling regions
  • Search and mark → maps to finding and replacing
  • Event bindings → maps to keyboard shortcuts

Key Concepts:

  • Text Widget: “Practical Programming in Tcl and Tk” Chapter 23 - Welch
  • Text Indices: Tcl Wiki text widget documentation
  • Syntax Highlighting: Regular expressions + tags

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 2, 4 completed.

Real world outcome:

┌─────────────────────────────────────────────────────────────┐
│ File  Edit  Search  View  Help                               │
├─────────────────────────────────────────────────────────────┤
│ [hello.tcl] [utils.tcl] [+]                                  │
├────┬────────────────────────────────────────────────────────┤
│  1 │ #!/usr/bin/env tclsh                                   │
│  2 │                                                         │
│  3 │ # A simple Tcl script                                  │
│  4 │ proc greet {name} {                                    │
│  5 │     puts "Hello, $name!"                               │
│  6 │ }                                                       │
│  7 │                                                         │
│  8 │ set message "Welcome to Tcl"                           │
│  9 │ puts $message                                          │
│ 10 │                                                         │
│ 11 │ foreach item {apple banana cherry} {                   │
│ 12 │     greet $item                                        │
│ 13 │ }                                                       │
├────┴────────────────────────────────────────────────────────┤
│ Line: 4  Col: 12 │ UTF-8 │ LF │ Tcl │ INS                    │
└─────────────────────────────────────────────────────────────┘

Colors: proc=blue, strings=red, comments=green, variables=purple

Implementation Hints:

Text widget basics:

# Create text widget with scrollbar
text .t -yscrollcommand {.sb set} \
        -wrap none \
        -undo true \
        -font {Consolas 11}
scrollbar .sb -command {.t yview}

pack .sb -side right -fill y
pack .t -side left -fill both -expand 1

# Insert text
.t insert end "Hello, World!\n"
.t insert 1.0 "Start: "  ;# Insert at beginning

# Get text
set all_text [.t get 1.0 end]
set line1 [.t get 1.0 "1.0 lineend"]
set selection [.t get sel.first sel.last]

# Delete text
.t delete 1.0 1.6       ;# Delete from 1.0 to 1.6
.t delete sel.first sel.last  ;# Delete selection

# Text indices
# line.char - e.g., 1.0 = first line, first char
# end - end of text
# "1.0 + 5 chars" - 5 chars after 1.0
# "1.0 lineend" - end of line 1
# "end - 1 char" - before final newline
# sel.first, sel.last - selection bounds
# insert - insertion cursor position

Tags for syntax highlighting:

# Define tags with styles
.t tag configure keyword -foreground blue -font {Consolas 11 bold}
.t tag configure string -foreground darkred
.t tag configure comment -foreground darkgreen -font {Consolas 11 italic}
.t tag configure variable -foreground purple
.t tag configure proc_name -foreground navy

# Apply tag to range
.t tag add keyword 4.0 4.4  ;# "proc" on line 4

# Remove tag
.t tag remove keyword 4.0 4.4

# Find and tag all occurrences
proc highlight_all {pattern tag} {
    set start 1.0
    while {1} {
        set pos [.t search -regexp -count length $pattern $start end]
        if {$pos eq ""} break
        .t tag add $tag $pos "$pos + $length chars"
        set start "$pos + $length chars"
    }
}

Syntax highlighting for Tcl:

proc highlight_tcl {} {
    # Clear existing highlights
    foreach tag {keyword string comment variable proc_name} {
        .t tag remove $tag 1.0 end
    }

    # Keywords
    set keywords {proc set if else elseif while for foreach switch
                  return break continue catch try global variable
                  namespace package expr puts gets incr append
                  lappend lindex linsert lrange list llength lsort}
    foreach kw $keywords {
        set start 1.0
        while {1} {
            set pos [.t search -regexp "\\m$kw\\M" $start end]
            if {$pos eq ""} break
            .t tag add keyword $pos "$pos + [string length $kw] chars"
            set start "$pos + 1 char"
        }
    }

    # Comments (# to end of line)
    set start 1.0
    while {1} {
        set pos [.t search -regexp {#[^\n]*} $start end]
        if {$pos eq ""} break
        set line_end [.t index "$pos lineend"]
        .t tag add comment $pos $line_end
        set start "$line_end + 1 char"
    }

    # Strings (double quotes)
    set start 1.0
    while {1} {
        set pos [.t search -regexp {"[^"]*"} $start end]
        if {$pos eq ""} break
        # Find the closing quote
        set end_pos [.t search "\"" "$pos + 1 char" end]
        if {$end_pos eq ""} break
        .t tag add string $pos "$end_pos + 1 char"
        set start "$end_pos + 1 char"
    }

    # Variables ($name)
    set start 1.0
    while {1} {
        set pos [.t search -regexp {\$\w+} $start end]
        if {$pos eq ""} break
        set match [.t get $pos "$pos wordend"]
        .t tag add variable $pos "$pos + [string length $match] chars"
        set start "$pos + 1 char"
    }
}

# Trigger highlighting on text changes
.t edit modified false
bind .t <<Modified>> {
    if {[.t edit modified]} {
        after 100 highlight_tcl
        .t edit modified false
    }
}

Line numbers:

# Create line number widget
text .linenum -width 4 -padx 5 -takefocus 0 -state disabled \
              -bg lightgray -fg gray30 \
              -font {Consolas 11}

proc update_line_numbers {} {
    .linenum configure -state normal
    .linenum delete 1.0 end

    set line_count [.t count -lines 1.0 end]
    for {set i 1} {$i <= $line_count} {incr i} {
        .linenum insert end "$i\n"
    }

    .linenum configure -state disabled

    # Sync scroll position
    set yview [.t yview]
    .linenum yview moveto [lindex $yview 0]
}

# Sync line numbers with main text
bind .t <KeyRelease> update_line_numbers
bind .t <Configure> update_line_numbers
.t configure -yscrollcommand {sync_scroll}

proc sync_scroll {args} {
    .sb set {*}$args
    .linenum yview moveto [lindex [.t yview] 0]
}

Find/replace dialog:

proc find_dialog {} {
    if {[winfo exists .find]} {
        raise .find
        return
    }

    toplevel .find
    wm title .find "Find & Replace"

    label .find.fl -text "Find:"
    entry .find.fe -textvariable ::find_text -width 30
    label .find.rl -text "Replace:"
    entry .find.re -textvariable ::replace_text -width 30

    button .find.next -text "Find Next" -command find_next
    button .find.replace -text "Replace" -command replace_one
    button .find.replaceall -text "Replace All" -command replace_all

    grid .find.fl .find.fe -sticky w -padx 5 -pady 5
    grid .find.rl .find.re -sticky w -padx 5 -pady 5
    grid .find.next .find.replace .find.replaceall -pady 10
}

proc find_next {} {
    global find_text
    set pos [.t search -forwards $find_text insert end]
    if {$pos ne ""} {
        .t tag remove sel 1.0 end
        .t tag add sel $pos "$pos + [string length $find_text] chars"
        .t mark set insert "$pos + [string length $find_text] chars"
        .t see $pos
    }
}

Learning milestones:

  1. Text editing works → You understand the text widget
  2. Syntax highlighting works → You understand tags
  3. Line numbers sync → You understand scroll coordination
  4. Find/replace works → You understand search in text

Project 7: Database Browser with SQLite

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Database / Treeview Widget
  • Software or Tool: Database Browser
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch + SQLite documentation

What you’ll build: A SQLite database browser with table listing, query execution, result display in a treeview, and data editing—demonstrating database integration and the ttk::treeview widget.

Why it teaches database + ttk: Many Tcl applications need database access. The treeview widget from ttk (themed Tk) is essential for displaying hierarchical and tabular data.

Core challenges you’ll face:

  • SQLite integration → maps to tdbc::sqlite3 or sqlite3 package
  • Treeview widget → maps to columns, rows, selection
  • Query execution → maps to prepared statements
  • Data editing → maps to inline editing in treeview

Key Concepts:

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Project 4 completed, basic SQL knowledge.

Real world outcome:

┌─────────────────────────────────────────────────────────────┐
│ File  Database  Query  Help                                  │
├─────────────────────────────────────────────────────────────┤
│ Database: /home/user/myapp.db                                │
├──────────────┬──────────────────────────────────────────────┤
│ Tables       │  Query                                        │
│ ┌──────────┐ │ ┌────────────────────────────────────────┐   │
│ │ users    │ │ │ SELECT * FROM users WHERE age > 25     │   │
│ │ products │ │ └────────────────────────────────────────┘   │
│ │ orders   │ │ [Execute] [Clear] [History ▼]                │
│ │ logs     │ │                                               │
│ └──────────┘ ├──────────────────────────────────────────────┤
│              │  Results (4 rows)                             │
│ Schema       │ ┌────┬──────────┬─────┬──────────────────┐   │
│ ┌──────────┐ │ │ id │   name   │ age │      email       │   │
│ │id INTEGER│ │ ├────┼──────────┼─────┼──────────────────┤   │
│ │name TEXT │ │ │  1 │ Alice    │  30 │ alice@mail.com   │   │
│ │age INT   │ │ │  2 │ Bob      │  28 │ bob@mail.com     │   │
│ │email TEXT│ │ │  5 │ Charlie  │  35 │ charlie@mail.com │   │
│ └──────────┘ │ │  7 │ Diana    │  42 │ diana@mail.com   │   │
│              │ └────┴──────────┴─────┴──────────────────┘   │
├──────────────┴──────────────────────────────────────────────┤
│ Query executed in 0.003s │ 4 rows returned                   │
└─────────────────────────────────────────────────────────────┘

Implementation Hints:

SQLite in Tcl:

# Load SQLite package
package require sqlite3

# Open database (creates if doesn't exist)
sqlite3 db "myapp.db"

# Execute SQL
db eval {CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER,
    email TEXT
)}

# Insert data
db eval {INSERT INTO users (name, age, email)
         VALUES ('Alice', 30, 'alice@mail.com')}

# Query data
set results [db eval {SELECT * FROM users}]
;# Returns flat list: 1 Alice 30 alice@mail.com 2 Bob 28 bob@mail.com...

# Query with row processing
db eval {SELECT * FROM users} row {
    puts "User: $row(name), Age: $row(age)"
}

# Parameterized queries (safe!)
set name "Alice"
db eval {SELECT * FROM users WHERE name = $name}

# Get table names
set tables [db eval {SELECT name FROM sqlite_master WHERE type='table'}]

# Get table schema
set schema [db eval {PRAGMA table_info(users)}]

# Close database
db close

Treeview widget:

package require Tk

# Create treeview with scrollbars
ttk::frame .tv_frame
ttk::treeview .tv_frame.tree -columns {id name age email} -show headings \
    -yscrollcommand {.tv_frame.vsb set} \
    -xscrollcommand {.tv_frame.hsb set}
ttk::scrollbar .tv_frame.vsb -orient vertical -command {.tv_frame.tree yview}
ttk::scrollbar .tv_frame.hsb -orient horizontal -command {.tv_frame.tree xview}

grid .tv_frame.tree -row 0 -column 0 -sticky nsew
grid .tv_frame.vsb -row 0 -column 1 -sticky ns
grid .tv_frame.hsb -row 1 -column 0 -sticky ew
grid columnconfigure .tv_frame 0 -weight 1
grid rowconfigure .tv_frame 0 -weight 1

# Configure columns
.tv_frame.tree heading id -text "ID" -anchor w
.tv_frame.tree heading name -text "Name" -anchor w
.tv_frame.tree heading age -text "Age" -anchor e
.tv_frame.tree heading email -text "Email" -anchor w

.tv_frame.tree column id -width 50
.tv_frame.tree column name -width 100
.tv_frame.tree column age -width 50
.tv_frame.tree column email -width 200

# Insert data
.tv_frame.tree insert {} end -id row1 -values {1 "Alice" 30 "alice@mail.com"}
.tv_frame.tree insert {} end -id row2 -values {2 "Bob" 28 "bob@mail.com"}

# Get selected items
set selected [.tv_frame.tree selection]
foreach item $selected {
    set values [.tv_frame.tree item $item -values]
    puts "Selected: $values"
}

# Handle selection
bind .tv_frame.tree <<TreeviewSelect>> {
    set selected [.tv_frame.tree selection]
    show_row_details $selected
}

# Clear treeview
proc clear_results {} {
    .tv_frame.tree delete [.tv_frame.tree children {}]
}

Query execution and display:

proc execute_query {} {
    global db query_text

    # Clear previous results
    clear_results

    # Get query from text widget
    set sql [$query_text get 1.0 "end -1 char"]
    if {[string trim $sql] eq ""} return

    # Execute and measure time
    set start [clock microseconds]

    if {[catch {
        # Get column names from first row
        set first 1
        db eval $sql row {
            if {$first} {
                # Configure columns from query result
                set cols $row(*)
                .tv_frame.tree configure -columns $cols
                foreach col $cols {
                    .tv_frame.tree heading $col -text $col -anchor w
                    .tv_frame.tree column $col -width 100
                }
                set first 0
            }

            # Build values list
            set values {}
            foreach col $row(*) {
                lappend values $row($col)
            }
            .tv_frame.tree insert {} end -values $values
        }
    } err]} {
        tk_messageBox -icon error -title "Query Error" -message $err
        return
    }

    set elapsed [expr {([clock microseconds] - $start) / 1000.0}]
    set row_count [llength [.tv_frame.tree children {}]]
    update_status "Query executed in ${elapsed}ms | $row_count rows returned"
}

Table browser:

proc load_tables {} {
    global db
    .tables.list delete 0 end

    set tables [db eval {
        SELECT name FROM sqlite_master
        WHERE type='table'
        ORDER BY name
    }]

    foreach table $tables {
        .tables.list insert end $table
    }
}

proc show_table_schema {table} {
    global db
    .schema.text configure -state normal
    .schema.text delete 1.0 end

    db eval "PRAGMA table_info($table)" col {
        set type $col(type)
        set notnull [expr {$col(notnull) ? " NOT NULL" : ""}]
        set pk [expr {$col(pk) ? " PRIMARY KEY" : ""}]
        .schema.text insert end "$col(name) $type$notnull$pk\n"
    }

    .schema.text configure -state disabled
}

bind .tables.list <<ListboxSelect>> {
    set idx [.tables.list curselection]
    if {$idx ne ""} {
        set table [.tables.list get $idx]
        show_table_schema $table
        # Auto-fill query
        $query_text delete 1.0 end
        $query_text insert 1.0 "SELECT * FROM $table LIMIT 100"
    }
}

Learning milestones:

  1. SQLite connects and queries → You understand database access
  2. Treeview displays results → You understand ttk widgets
  3. Dynamic columns work → You understand flexible display
  4. Query editing works → You’ve built a useful tool

Project 8: Network Chat Application

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Sockets / Event-Driven Programming
  • Software or Tool: Chat Application
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapter 17)

What you’ll build: A TCP-based chat application with server and client components, demonstrating Tcl’s event-driven socket programming and fileevent handling.

Why it teaches networking: Tcl’s socket and fileevent commands make network programming remarkably simple. The event-driven model (no threads needed!) is powerful and educational.

Core challenges you’ll face:

  • Socket creation → maps to server and client sockets
  • Event-driven I/O → maps to fileevent for non-blocking
  • Protocol design → maps to message framing
  • Multiple clients → maps to channel management

Key Concepts:

  • Sockets: “Practical Programming in Tcl and Tk” Chapter 17 - Welch
  • Fileevent: Event-driven I/O in Tcl
  • Channels: Tcl’s unified I/O abstraction

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 3-4 completed.

Real world outcome:

Server Terminal:
$ tclsh chat_server.tcl 9000
Chat Server started on port 9000
[12:01:15] Client connected from 192.168.1.10
[12:01:18] Client connected from 192.168.1.11
[12:01:20] <Alice> Hello everyone!
[12:01:22] <Bob> Hi Alice!
[12:01:45] Client 192.168.1.10 disconnected

Client GUI:
┌─────────────────────────────────────────────────────┐
│ TclChat - Connected to localhost:9000                │
├─────────────────────────────────────────────────────┤
│                                                      │
│ [12:01:20] Alice: Hello everyone!                   │
│ [12:01:22] Bob: Hi Alice!                           │
│ [12:01:25] Alice: How's your Tcl project going?     │
│ [12:01:28] Bob: Great! Just finished the GUI.       │
│                                                      │
├─────────────────────────────────────────────────────┤
│ Online Users: Alice, Bob, Charlie                    │
├─────────────────────────────────────────────────────┤
│ [Type message here...                    ] [Send]   │
└─────────────────────────────────────────────────────┘

Implementation Hints:

Server:

#!/usr/bin/env tclsh

package require Tcl 8.6

set clients [dict create]  ;# socket -> {name addr}

proc accept_client {sock addr port} {
    global clients
    puts "Client connected from $addr:$port"

    fconfigure $sock -buffering line -blocking 0
    fileevent $sock readable [list handle_client $sock]

    dict set clients $sock [dict create addr $addr name "Guest_$port"]
    broadcast "*** New user connected from $addr ***"
}

proc handle_client {sock} {
    global clients

    if {[eof $sock] || [catch {gets $sock line} count] || $count < 0} {
        disconnect_client $sock
        return
    }

    set line [string trim $line]
    if {$line eq ""} return

    # Parse commands
    if {[string match "/nick *" $line]} {
        set newname [string range $line 6 end]
        set oldname [dict get [dict get $clients $sock] name]
        dict set clients $sock name $newname
        broadcast "*** $oldname is now known as $newname ***"
    } elseif {[string match "/quit" $line]} {
        disconnect_client $sock
    } else {
        set name [dict get [dict get $clients $sock] name]
        broadcast "<$name> $line"
    }
}

proc disconnect_client {sock} {
    global clients
    if {[dict exists $clients $sock]} {
        set name [dict get [dict get $clients $sock] name]
        dict unset clients $sock
        catch {close $sock}
        broadcast "*** $name has left ***"
        puts "Client $name disconnected"
    }
}

proc broadcast {message} {
    global clients
    set timestamp [clock format [clock seconds] -format "%H:%M:%S"]
    set full_msg "\[$timestamp\] $message"

    puts $full_msg  ;# Log to server console

    dict for {sock info} $clients {
        catch {puts $sock $full_msg}
    }
}

# Start server
set port [lindex $argv 0]
if {$port eq ""} {set port 9000}

socket -server accept_client $port
puts "Chat Server started on port $port"
vwait forever

Client:

#!/usr/bin/env wish

package require Tk

set sock ""
set nickname "Guest"

# GUI
wm title . "TclChat"
wm geometry . 500x400

# Chat display
text .chat -state disabled -wrap word -yscrollcommand {.sb set}
scrollbar .sb -command {.chat yview}

# User list
listbox .users -width 20 -height 10

# Input area
frame .input
entry .input.msg -textvariable message -width 40
button .input.send -text "Send" -command send_message
button .input.connect -text "Connect" -command connect_dialog

# Layout
grid .chat -row 0 -column 0 -sticky nsew -padx 5 -pady 5
grid .sb -row 0 -column 1 -sticky ns
grid .users -row 0 -column 2 -sticky ns -padx 5 -pady 5
grid .input -row 1 -column 0 -columnspan 3 -sticky ew -padx 5 -pady 5
pack .input.msg -side left -fill x -expand 1
pack .input.send -side left -padx 5
pack .input.connect -side left

grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1

# Bindings
bind .input.msg <Return> send_message

proc connect_dialog {} {
    global sock nickname

    toplevel .conn
    wm title .conn "Connect"

    label .conn.hl -text "Host:"
    entry .conn.host -textvariable ::host -width 20
    set ::host "localhost"

    label .conn.pl -text "Port:"
    entry .conn.port -textvariable ::port -width 10
    set ::port "9000"

    label .conn.nl -text "Nickname:"
    entry .conn.nick -textvariable nickname -width 20

    button .conn.ok -text "Connect" -command {
        connect_to_server $::host $::port
        destroy .conn
    }

    grid .conn.hl .conn.host -padx 5 -pady 5
    grid .conn.pl .conn.port -padx 5 -pady 5
    grid .conn.nl .conn.nick -padx 5 -pady 5
    grid .conn.ok -columnspan 2 -pady 10
}

proc connect_to_server {host port} {
    global sock nickname

    if {[catch {socket $host $port} sock]} {
        append_chat "*** Connection failed: $sock ***"
        return
    }

    fconfigure $sock -buffering line -blocking 0
    fileevent $sock readable receive_message

    append_chat "*** Connected to $host:$port ***"
    wm title . "TclChat - $nickname@$host:$port"

    # Set nickname
    puts $sock "/nick $nickname"
}

proc receive_message {} {
    global sock

    if {[eof $sock]} {
        append_chat "*** Disconnected from server ***"
        catch {close $sock}
        set sock ""
        return
    }

    if {[gets $sock line] >= 0} {
        append_chat $line
    }
}

proc send_message {} {
    global sock message

    if {$sock eq "" || $message eq ""} return

    puts $sock $message
    set message ""
}

proc append_chat {text} {
    .chat configure -state normal
    .chat insert end "$text\n"
    .chat see end
    .chat configure -state disabled
}

# Initial state
append_chat "*** Welcome to TclChat ***"
append_chat "*** Click 'Connect' to join a server ***"

Learning milestones:

  1. Server accepts connections → You understand server sockets
  2. Messages broadcast → You understand multi-client handling
  3. GUI updates on receive → You understand fileevent
  4. Protocol works → You understand message framing

Project 9: System Automation with Expect

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Expect
  • Alternative Programming Languages: Python (pexpect)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Process Automation / SSH
  • Software or Tool: Automation Scripts
  • Main Book: “Exploring Expect” by Don Libes

What you’ll build: A suite of automation scripts using Expect: SSH login automation, interactive installer automation, and a network device configuration tool.

Why it teaches Expect: Expect is Tcl’s killer application for automation. It controls interactive programs that can’t be scripted normally. Understanding Expect makes you a system automation expert.

Core challenges you’ll face:

  • spawn, expect, send → maps to the core Expect commands
  • Pattern matching → maps to expecting multiple possibilities
  • Timeout handling → maps to dealing with delays and failures
  • Secure credential handling → maps to not hardcoding passwords

Resources for key challenges:

Key Concepts:

  • Expect Basics: Expect Wikipedia
  • spawn/expect/send: Core Expect commands
  • Timeouts: Handling delays and failures

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Projects 1-2 completed, familiarity with SSH.

Real world outcome:

$ ./ssh_deploy.exp servers.txt

Deploying configuration to 5 servers...

[1/5] webserver1.example.com
  Connecting... OK
  Uploading config... OK
  Restarting service... OK
  Verifying... OK ✓

[2/5] webserver2.example.com
  Connecting... OK
  Uploading config... OK
  Restarting service... OK
  Verifying... OK ✓

[3/5] dbserver1.example.com
  Connecting... FAILED (timeout after 30s)
  Skipping...

[4/5] appserver1.example.com
  Connecting... OK
  Uploading config... OK
  Restarting service... FAILED (permission denied)
  Rolling back... OK

[5/5] appserver2.example.com
  Connecting... OK
  Uploading config... OK
  Restarting service... OK
  Verifying... OK ✓

Summary: 3 succeeded, 2 failed

Implementation Hints:

Basic Expect:

#!/usr/bin/expect -f

# spawn starts an interactive program
spawn ssh user@hostname

# expect waits for patterns
expect {
    "password:" {
        # send types to the program
        send "mypassword\r"
        exp_continue  ;# Continue expecting
    }
    "yes/no" {
        # Handle SSH host key prompt
        send "yes\r"
        exp_continue
    }
    "$ " {
        # We're logged in!
        puts "Login successful"
    }
    timeout {
        puts "Connection timed out"
        exit 1
    }
}

# Run commands
send "ls -la\r"
expect "$ "

send "exit\r"
expect eof

SSH automation script:

#!/usr/bin/expect -f

# Configuration
set timeout 30
log_user 0  ;# Suppress output (set to 1 for debugging)

# Get credentials
proc get_password {} {
    stty -echo
    send_user "Password: "
    expect_user -re "(.*)\n"
    set password $expect_out(1,string)
    stty echo
    send_user "\n"
    return $password
}

proc ssh_connect {host user password} {
    spawn ssh -o StrictHostKeyChecking=no $user@$host

    expect {
        "password:" {
            send "$password\r"
            exp_continue
        }
        "Permission denied" {
            return -code error "Authentication failed"
        }
        "$ " {
            return $spawn_id
        }
        timeout {
            return -code error "Connection timeout"
        }
    }
}

proc ssh_command {spawn_id cmd} {
    set timeout 60
    send -i $spawn_id "$cmd\r"
    expect -i $spawn_id {
        -re "(.*)\r\n.*\\$ " {
            return $expect_out(1,string)
        }
        timeout {
            return -code error "Command timeout"
        }
    }
}

proc ssh_close {spawn_id} {
    send -i $spawn_id "exit\r"
    expect -i $spawn_id eof
}

# Main script
set user [lindex $argv 0]
set host [lindex $argv 1]

if {$user eq "" || $host eq ""} {
    puts "Usage: ssh_auto.exp user host"
    exit 1
}

set password [get_password]

puts "Connecting to $user@$host..."

if {[catch {ssh_connect $host $user $password} spawn_id]} {
    puts "Failed: $spawn_id"
    exit 1
}

puts "Connected!"

# Run commands
puts "Uptime: [ssh_command $spawn_id uptime]"
puts "Disk usage: [ssh_command $spawn_id {df -h /}]"

ssh_close $spawn_id
puts "Done."

Multi-server deployment:

#!/usr/bin/expect -f

proc deploy_to_server {host user password config_file} {
    puts "  Connecting..."
    if {[catch {ssh_connect $host $user $password} sid]} {
        puts "  FAILED: $sid"
        return 0
    }
    puts "  OK"

    puts -nonewline "  Uploading config..."
    flush stdout

    # SCP the config file
    spawn scp $config_file $user@$host:/tmp/config.new
    expect {
        "password:" { send "$password\r"; exp_continue }
        "100%" { puts " OK" }
        timeout { puts " FAILED (timeout)"; return 0 }
    }

    # Apply config
    puts -nonewline "  Applying config..."
    flush stdout
    if {[catch {ssh_command $sid "sudo cp /tmp/config.new /etc/app/config"} result]} {
        puts " FAILED"
        return 0
    }
    puts " OK"

    # Restart service
    puts -nonewline "  Restarting service..."
    flush stdout
    if {[catch {ssh_command $sid "sudo systemctl restart myapp"} result]} {
        puts " FAILED"
        # Rollback
        ssh_command $sid "sudo systemctl restart myapp --rollback"
        return 0
    }
    puts " OK"

    ssh_close $sid
    return 1
}

# Read server list
set servers_file [lindex $argv 0]
set config_file [lindex $argv 1]

set fh [open $servers_file r]
set servers [split [read $fh] "\n"]
close $fh

set password [get_password]

set succeeded 0
set failed 0
set total [llength $servers]
set n 0

foreach server $servers {
    if {[string trim $server] eq ""} continue
    lassign [split $server :] host user
    if {$user eq ""} {set user "deploy"}

    incr n
    puts "\n\[$n/$total\] $host"

    if {[deploy_to_server $host $user $password $config_file]} {
        incr succeeded
    } else {
        incr failed
    }
}

puts "\nSummary: $succeeded succeeded, $failed failed"

Learning milestones:

  1. Basic SSH login works → You understand spawn/expect/send
  2. Commands execute remotely → You understand interaction
  3. Errors are handled → You understand expect alternatives
  4. Multi-server works → You’ve built a useful tool

Project 10: Plugin-Extensible Application

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python, Lua
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 4: Expert
  • Knowledge Area: Namespaces / Safe Interpreters
  • Software or Tool: Extensible Application
  • Main Book: “Practical Programming in Tcl and Tk” by Brent Welch (Chapters 14-15)

What you’ll build: An application with a plugin architecture using Tcl’s namespace and safe interpreter features, allowing third-party extensions to be loaded securely.

Why it teaches extensibility: Tcl was designed for embedding and extending. Its interpreter-within-interpreter model and safe interpreters enable powerful plugin systems. This is why Tcl is the scripting language for CAD tools.

Core challenges you’ll face:

  • Namespaces → maps to organizing code and avoiding conflicts
  • Safe interpreters → maps to sandboxed execution
  • Package system → maps to distributing plugins
  • API design → maps to what plugins can access

Key Concepts:

  • Namespaces: “Practical Programming in Tcl and Tk” Chapter 14 - Welch
  • Safe Interpreters: “Practical Programming in Tcl and Tk” Chapter 15 - Welch
  • Package System: “Practical Programming in Tcl and Tk” Chapter 12 - Welch

Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: Projects 1-6 completed.

Real world outcome:

$ tclsh pluginhost.tcl

PluginHost v1.0 - Extensible Application Framework
================================================

Loading plugins from ./plugins/...
  [✓] weather.tcl - Weather Widget
  [✓] calculator.tcl - Calculator Plugin
  [✓] notes.tcl - Quick Notes
  [✗] malicious.tcl - BLOCKED: attempted file system access

Loaded 3 plugins, blocked 1

Available commands:
  /weather <city>     - Get weather for city
  /calc <expression>  - Calculate expression
  /note <text>        - Save a note
  /notes              - List all notes
  /plugins            - List loaded plugins
  /reload             - Reload all plugins

> /weather London
Weather for London:
  Temperature: 15°C
  Conditions: Partly cloudy
  Humidity: 65%

> /calc sin(3.14159/2)
Result: 1.0

> /plugins
Loaded plugins:
  1. weather v1.2 by WeatherCo - Weather Widget
  2. calculator v2.0 by MathTools - Calculator Plugin
  3. notes v1.0 by NoteApp - Quick Notes

Implementation Hints:

Namespaces:

# Create namespace
namespace eval myapp {
    # Namespace variables
    variable version "1.0"
    variable plugins {}

    # Namespace procedures
    proc init {} {
        variable version
        puts "MyApp v$version starting..."
    }

    proc register_plugin {name} {
        variable plugins
        lappend plugins $name
    }
}

# Access from outside
myapp::init
puts $myapp::version

# Export procedures (make callable without prefix)
namespace eval myapp {
    namespace export init register_plugin
}
namespace import myapp::*
init  ;# Now works without myapp::

Safe interpreters:

# Create a safe interpreter (sandbox)
set safe [interp create -safe]

# Safe interpreters cannot:
# - Access files (open, file, source)
# - Execute programs (exec)
# - Load packages (load, package require)
# - Access environment variables

# Add allowed commands via aliases
interp alias $safe puts {} safe_puts
interp alias $safe get_data {} safe_get_data

proc safe_puts {args} {
    puts "PLUGIN OUTPUT: $args"
}

proc safe_get_data {key} {
    # Return only allowed data
    switch $key {
        version { return "1.0" }
        default { return "" }
    }
}

# Evaluate code in safe interpreter
interp eval $safe {
    puts "Hello from sandbox!"
    puts "Version: [get_data version]"

    # These would fail:
    # open /etc/passwd  ;# Error: invalid command name "open"
    # exec rm -rf /     ;# Error: invalid command name "exec"
}

# Delete safe interpreter
interp delete $safe

Plugin system:

#!/usr/bin/env tclsh

namespace eval PluginHost {
    variable plugins {}
    variable safe_interps {}
    variable plugin_api {
        puts log get_config register_command
    }

    proc load_plugin {path} {
        variable plugins
        variable safe_interps
        variable plugin_api

        set name [file rootname [file tail $path]]
        puts -nonewline "  Loading $name... "

        # Create safe interpreter for this plugin
        set safe [interp create -safe]
        dict set safe_interps $name $safe

        # Set up API aliases
        interp alias $safe log {} PluginHost::plugin_log $name
        interp alias $safe get_config {} PluginHost::plugin_get_config $name
        interp alias $safe register_command {} PluginHost::plugin_register_command $name

        # Load plugin code
        set fh [open $path r]
        set code [read $fh]
        close $fh

        # Execute in safe interpreter
        if {[catch {interp eval $safe $code} err]} {
            puts "FAILED"
            puts "    Error: $err"
            interp delete $safe
            dict unset safe_interps $name
            return 0
        }

        # Get plugin metadata
        set metadata [interp eval $safe {
            if {[info exists plugin_name]} {
                list name $plugin_name version $plugin_version \
                     author $plugin_author description $plugin_description
            } else {
                list name "Unknown" version "0.0" author "Unknown" description ""
            }
        }]

        dict set plugins $name $metadata
        puts "OK"
        return 1
    }

    proc plugin_log {plugin_name args} {
        puts "\[$plugin_name\] $args"
    }

    proc plugin_get_config {plugin_name key} {
        # Return configuration for plugin
        switch $key {
            api_key { return "demo_key_123" }
            default { return "" }
        }
    }

    proc plugin_register_command {plugin_name cmd proc_name} {
        variable safe_interps
        set safe [dict get $safe_interps $plugin_name]

        # Register command handler
        proc ::cmd_$cmd {args} [subst {
            interp eval $safe [list $proc_name {*}\$args]
        }]

        puts "    Registered command: /$cmd"
    }

    proc load_all_plugins {dir} {
        puts "Loading plugins from $dir/..."
        set loaded 0
        set blocked 0

        foreach file [glob -nocomplain -directory $dir *.tcl] {
            if {[load_plugin $file]} {
                incr loaded
            } else {
                incr blocked
            }
        }

        puts "\nLoaded $loaded plugins, blocked $blocked"
    }

    proc run_command {input} {
        if {![string match "/*" $input]} {
            puts "Commands start with /"
            return
        }

        set parts [split [string range $input 1 end]]
        set cmd [lindex $parts 0]
        set args [lrange $parts 1 end]

        if {[info commands ::cmd_$cmd] ne ""} {
            ::cmd_$cmd {*}$args
        } else {
            puts "Unknown command: /$cmd"
        }
    }
}

# Example plugin (plugins/calculator.tcl):
# set plugin_name "Calculator"
# set plugin_version "1.0"
# set plugin_author "MathTools"
# set plugin_description "Basic calculator"
#
# proc calculate {expr} {
#     log "Calculating: $expr"
#     expr $expr
# }
#
# register_command "calc" calculate

# Main
PluginHost::load_all_plugins "./plugins"

puts "\nEnter commands (type /quit to exit):"
while {1} {
    puts -nonewline "> "
    flush stdout
    gets stdin input
    if {$input eq "/quit"} break
    PluginHost::run_command $input
}

Learning milestones:

  1. Namespaces organize code → You understand code organization
  2. Safe interps block dangerous commands → You understand sandboxing
  3. Plugins register commands → You understand extensibility
  4. Malicious plugins are blocked → You understand security

Project 11: TclOO - Object-Oriented Calculator

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/TclOO
  • Alternative Programming Languages: Python
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: OOP / TclOO
  • Software or Tool: OO Application
  • Main Book: “The Tcl Programming Language” by Ashok Nadkarni (TclOO chapter)

What you’ll build: A scientific calculator using TclOO with classes for expressions, operations, memory, and history—demonstrating modern object-oriented Tcl.

Why it teaches TclOO: TclOO (added in Tcl 8.6) provides a modern, powerful OO system. Understanding it is essential for modern Tcl development and working with OO extensions like itcl.

Core challenges you’ll face:

  • Class definition → maps to oo::class create
  • Constructors/destructors → maps to constructor/destructor methods
  • Inheritance → maps to superclass
  • Mixins and filters → maps to advanced OO features

Resources for key challenges:

Key Concepts:

  • TclOO Basics: Tcl 8.6+ documentation
  • Classes and Objects: oo::class, oo::object
  • Mixins: Dynamic class composition

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Projects 1-3 completed.

Real world outcome:

# Using the OO Calculator
set calc [Calculator new]

$calc push 5
$calc push 3
$calc add
puts [$calc result]  ;# 8

$calc push 10
$calc multiply
puts [$calc result]  ;# 80

$calc memory_store
$calc clear
$calc memory_recall
puts [$calc result]  ;# 80

puts [$calc history]
;# 1: 5 + 3 = 8
;# 2: 8 * 10 = 80
;# 3: M+ 80
;# 4: MR = 80

# Scientific operations
set sci [ScientificCalculator new]
$sci push 3.14159
$sci sin
puts [$sci result]  ;# 0.0000...

$calc destroy
$sci destroy

Implementation Hints:

TclOO basics:

package require TclOO

# Define a class
oo::class create Counter {
    # Instance variable
    variable count

    # Constructor
    constructor {{initial 0}} {
        set count $initial
    }

    # Methods
    method increment {} {
        incr count
    }

    method decrement {} {
        incr count -1
    }

    method value {} {
        return $count
    }

    method reset {} {
        set count 0
    }
}

# Create instance
set c [Counter new 10]
$c increment
puts [$c value]  ;# 11
$c destroy

Calculator with inheritance:

package require TclOO

# Base calculator class
oo::class create Calculator {
    variable stack memory history

    constructor {} {
        set stack {}
        set memory 0
        set history {}
    }

    method push {value} {
        lappend stack $value
    }

    method pop {} {
        if {[llength $stack] == 0} {
            error "Stack underflow"
        }
        set value [lindex $stack end]
        set stack [lrange $stack 0 end-1]
        return $value
    }

    method result {} {
        if {[llength $stack] == 0} {
            return 0
        }
        return [lindex $stack end]
    }

    method clear {} {
        set stack {}
    }

    # Binary operations
    method binary_op {op symbol} {
        set b [my pop]
        set a [my pop]
        set result [$op $a $b]
        my push $result
        my add_history "$a $symbol $b = $result"
        return $result
    }

    method add {} {
        my binary_op {{a b} {expr {$a + $b}}} "+"
    }

    method subtract {} {
        my binary_op {{a b} {expr {$a - $b}}} "-"
    }

    method multiply {} {
        my binary_op {{a b} {expr {$a * $b}}} "*"
    }

    method divide {} {
        my binary_op {{a b} {expr {$a / $b}}} "/"
    }

    # Memory operations
    method memory_store {} {
        set memory [my result]
        my add_history "M+ $memory"
    }

    method memory_recall {} {
        my push $memory
        my add_history "MR = $memory"
    }

    method memory_clear {} {
        set memory 0
    }

    # History
    method add_history {entry} {
        lappend history "[llength $history]: $entry"
    }

    method history {} {
        return [join $history "\n"]
    }
}

# Scientific calculator (inheritance)
oo::class create ScientificCalculator {
    superclass Calculator

    # Unary operations
    method unary_op {op name} {
        set a [my pop]
        set result [$op $a]
        my push $result
        my add_history "$name($a) = $result"
        return $result
    }

    method sin {} {
        my unary_op {{x} {expr {sin($x)}}} "sin"
    }

    method cos {} {
        my unary_op {{x} {expr {cos($x)}}} "cos"
    }

    method tan {} {
        my unary_op {{x} {expr {tan($x)}}} "tan"
    }

    method sqrt {} {
        my unary_op {{x} {expr {sqrt($x)}}} "sqrt"
    }

    method log {} {
        my unary_op {{x} {expr {log($x)}}} "ln"
    }

    method log10 {} {
        my unary_op {{x} {expr {log10($x)}}} "log"
    }

    method exp {} {
        my unary_op {{x} {expr {exp($x)}}} "exp"
    }

    method power {} {
        my binary_op {{a b} {expr {pow($a, $b)}}} "^"
    }

    # Constants
    method pi {} {
        my push [expr {acos(-1)}]
    }

    method e {} {
        my push [expr {exp(1)}]
    }
}

Mixins (dynamic class composition):

# Define a logging mixin
oo::class create Logging {
    method log {message} {
        puts "[clock format [clock seconds] -format %H:%M:%S] $message"
    }

    # Intercept method calls
    filter LoggingFilter

    method LoggingFilter {args} {
        my log "Calling: [self method] with $args"
        set result [next {*}$args]
        my log "Result: $result"
        return $result
    }
}

# Add logging to calculator
oo::define ScientificCalculator {
    mixin Logging
}

# Now all method calls are logged
set calc [ScientificCalculator new]
$calc push 5  ;# Logs: Calling: push with 5

Learning milestones:

  1. Classes and objects work → You understand TclOO basics
  2. Inheritance works → You understand superclass
  3. Methods call each other → You understand my and self
  4. Mixins add behavior → You understand advanced OO

Capstone Project: Complete Application - Personal Finance Tracker

  • File: LEARN_TCL_TK.md
  • Main Programming Language: Tcl/Tk
  • Alternative Programming Languages: Python
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 4: Expert
  • Knowledge Area: Complete Application
  • Software or Tool: Finance Application
  • Main Book: All previous resources combined

What you’ll build: A complete personal finance tracker with: transaction entry, category management, budget tracking, charts (via canvas), SQLite storage, import/export, and report generation.

Why this is the capstone: This integrates everything: GUI widgets, database, file I/O, canvas graphics, OO design, and proper application architecture. It’s a real, useful application.

Core challenges you’ll face:

  • Application architecture → maps to MVC pattern in Tcl
  • Complex GUI → maps to multiple windows, dialogs
  • Data visualization → maps to charts with canvas
  • Reports → maps to generating output

Key Concepts:

  • All previous projects
  • Application Design: MVC in Tcl
  • Data Visualization: Canvas charting

Difficulty: Expert Time estimate: 4-6 weeks Prerequisites: All previous projects.

Real world outcome:

┌─────────────────────────────────────────────────────────────────┐
│ Personal Finance Tracker                                         │
│ File  Edit  View  Reports  Help                                  │
├─────────────────────────────────────────────────────────────────┤
│ Balance: $4,250.00          Budget This Month: $2,000 remaining │
├──────────────────┬──────────────────────────────────────────────┤
│ Categories       │ Recent Transactions                           │
│ ┌──────────────┐ │ ┌────────────────────────────────────────┐   │
│ │ 💰 Income    │ │ │ Date       │ Description  │ Amount     │   │
│ │ 🏠 Housing   │ │ ├────────────┼──────────────┼────────────┤   │
│ │ 🍕 Food      │ │ │ 2024-01-15 │ Salary       │ +$5,000.00 │   │
│ │ 🚗 Transport │ │ │ 2024-01-14 │ Rent         │ -$1,200.00 │   │
│ │ 🎮 Entertain │ │ │ 2024-01-13 │ Groceries    │ -$150.00   │   │
│ │ 📱 Utilities │ │ │ 2024-01-12 │ Gas          │ -$45.00    │   │
│ └──────────────┘ │ │ 2024-01-11 │ Restaurant   │ -$65.00    │   │
│                  │ └────────────────────────────────────────┘   │
│ Spending Chart   ├──────────────────────────────────────────────┤
│ ┌──────────────┐ │ Add Transaction                               │
│ │    ████      │ │ Description: [                             ] │
│ │    ████ ██   │ │ Amount:      [$         ] [Income/Expense ▼] │
│ │ ██ ████ ████ │ │ Category:    [Select...                   ▼] │
│ │ ██ ████ ████ │ │ Date:        [2024-01-15                   ] │
│ │ H  F  T  E   │ │              [Add Transaction]               │
│ └──────────────┘ │                                               │
└──────────────────┴──────────────────────────────────────────────┘

Implementation Hints:

This project combines:

  1. TclOO for models (Transaction, Category, Budget)
  2. SQLite for persistence
  3. Tk widgets for UI (treeview, entry, menus)
  4. Canvas for charts
  5. Namespaces for organization

Application structure:

finance_tracker/
├── main.tcl           # Entry point
├── lib/
│   ├── models/
│   │   ├── transaction.tcl
│   │   ├── category.tcl
│   │   └── budget.tcl
│   ├── views/
│   │   ├── main_window.tcl
│   │   ├── transaction_form.tcl
│   │   ├── charts.tcl
│   │   └── reports.tcl
│   ├── controllers/
│   │   └── app_controller.tcl
│   └── database.tcl
└── data/
    └── finance.db

Model example (transaction.tcl):

package require TclOO

oo::class create Transaction {
    variable id date description amount category_id type

    constructor {args} {
        dict with args {
            set id [dict get $args id]
            set date [dict get $args date]
            set description [dict get $args description]
            set amount [dict get $args amount]
            set category_id [dict get $args category_id]
            set type [dict get $args type]  ;# income or expense
        }
    }

    method save {} {
        if {$id eq ""} {
            # Insert
            db eval {
                INSERT INTO transactions (date, description, amount, category_id, type)
                VALUES ($date, $description, $amount, $category_id, $type)
            }
            set id [db last_insert_rowid]
        } else {
            # Update
            db eval {
                UPDATE transactions
                SET date=$date, description=$description, amount=$amount,
                    category_id=$category_id, type=$type
                WHERE id=$id
            }
        }
    }

    method delete {} {
        db eval {DELETE FROM transactions WHERE id=$id}
    }

    # Class method for finding transactions
    self method find_all {{where ""}} {
        set sql "SELECT * FROM transactions"
        if {$where ne ""} {
            append sql " WHERE $where"
        }
        append sql " ORDER BY date DESC"

        set results {}
        db eval $sql row {
            lappend results [Transaction new \
                id $row(id) date $row(date) description $row(description) \
                amount $row(amount) category_id $row(category_id) type $row(type)]
        }
        return $results
    }

    self method find_by_month {year month} {
        my find_all "strftime('%Y-%m', date) = '$year-$month'"
    }
}

Chart with canvas:

proc draw_bar_chart {canvas data title} {
    $canvas delete all

    set width [winfo width $canvas]
    set height [winfo height $canvas]
    set margin 50
    set bar_width 40
    set spacing 20

    # Find max value for scaling
    set max_value 0
    foreach {label value} $data {
        if {$value > $max_value} {set max_value $value}
    }
    if {$max_value == 0} {set max_value 1}

    # Draw title
    $canvas create text [expr {$width/2}] 20 -text $title \
        -font {Arial 14 bold} -anchor n

    # Draw bars
    set x [expr {$margin + $bar_width/2}]
    set colors {#4CAF50 #2196F3 #FF9800 #E91E63 #9C27B0}
    set color_idx 0

    foreach {label value} $data {
        set bar_height [expr {($value / $max_value) * ($height - 2*$margin)}]
        set y1 [expr {$height - $margin}]
        set y2 [expr {$y1 - $bar_height}]

        set color [lindex $colors [expr {$color_idx % [llength $colors]}]]

        $canvas create rectangle \
            [expr {$x - $bar_width/2}] $y1 \
            [expr {$x + $bar_width/2}] $y2 \
            -fill $color -outline black

        # Value on top of bar
        $canvas create text $x [expr {$y2 - 5}] \
            -text [format "$%.0f" $value] -anchor s

        # Label below bar
        $canvas create text $x [expr {$y1 + 5}] \
            -text $label -anchor n

        incr x [expr {$bar_width + $spacing}]
        incr color_idx
    }
}

# Usage
set chart_data {
    Housing 1200
    Food 450
    Transport 200
    Entertainment 150
}
draw_bar_chart .chart $chart_data "Spending by Category"

Learning milestones:

  1. Database stores transactions → Data layer works
  2. UI displays and edits data → View layer works
  3. Charts visualize spending → Visualization works
  4. Reports generate → Complete feature set
  5. Application is usable → Production quality

Project Comparison Table

Project Difficulty Time Depth of Understanding Fun Factor
1. Calculator REPL Beginner Weekend ⭐⭐ ⭐⭐⭐
2. File Manager Intermediate 1 week ⭐⭐⭐ ⭐⭐⭐
3. Temp Converter (Tk) Beginner Weekend ⭐⭐ ⭐⭐⭐⭐
4. Todo List Intermediate 1 week ⭐⭐⭐ ⭐⭐⭐⭐
5. Drawing App Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
6. Text Editor Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
7. Database Browser Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
8. Chat Application Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
9. Expect Automation Advanced 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
10. Plugin System Expert 2-3 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
11. TclOO Calculator Advanced 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐
Capstone: Finance App Expert 4-6 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

Path A: The Complete Journey (3-4 months)

  1. Week 1: Projects 1-2 (Tcl Fundamentals)
  2. Week 2: Project 3 (First Tk GUI)
  3. Weeks 3-4: Projects 4-5 (Widgets and Canvas)
  4. Weeks 5-6: Project 6 (Text Widget mastery)
  5. Weeks 7-8: Projects 7-8 (Database and Networking)
  6. Weeks 9-10: Project 9 (Expect Automation)
  7. Weeks 11-12: Projects 10-11 (Advanced Tcl)
  8. Weeks 13-16: Capstone

Path B: GUI Focus (6-8 weeks)

1 → 3 → 4 → 5 → 6 → Capstone

Path C: System Administration Focus (4-6 weeks)

1 → 2 → 8 → 9 → (practical scripting for sysadmin)

Recommendation Based on Your Goals

If you’re learning Tcl for EDA tools (Vivado, etc.): Focus on Projects 1, 2, 9, 10. EDA tools use Tcl for scripting and automation.

If you want rapid GUI development: Follow Path B. Tk makes GUI development incredibly fast.

If you’re a sysadmin: Follow Path C. Expect is invaluable for automating SSH and interactive programs.


Essential Resources

Books

  1. “Practical Programming in Tcl and Tk” by Brent Welch, Ken Jones, Jeffrey Hobbs
    • The definitive Tcl/Tk book (“the bible”)
    • Covers everything from basics to C integration
    • 4th Edition covers Tcl 8.4
  2. “The Tcl Programming Language” by Ashok Nadkarni
    • Modern, comprehensive guide
    • Second Edition covers Tcl 9
    • Includes TclOO coverage
  3. “Exploring Expect” by Don Libes
    • The definitive Expect book
    • Essential for automation

Online Resources

Tools

  • tclsh: Interactive Tcl shell
  • wish: Tcl with Tk (windowing shell)
  • tkcon: Enhanced Tcl console
  • VS Code Tcl Extension: Modern IDE support with debugging

Summary

# Project Name Main Language
1 Interactive Calculator and REPL Explorer Tcl
2 File Manager and Text Processor Tcl
3 Temperature Converter (First Tk GUI) Tcl/Tk
4 Todo List Application Tcl/Tk
5 Drawing Application with Canvas Tcl/Tk
6 Text Editor with Syntax Highlighting Tcl/Tk
7 Database Browser with SQLite Tcl/Tk
8 Network Chat Application Tcl/Tk
9 System Automation with Expect Tcl/Expect
10 Plugin-Extensible Application Tcl
11 TclOO - Object-Oriented Calculator Tcl/TclOO
Capstone Personal Finance Tracker Tcl/Tk

Tcl’s simplicity is deceptive—its 12 rules hide remarkable power. Once you internalize “everything is a string” and “everything is a command,” you’ll find Tcl is one of the most flexible languages ever created. Start with the REPL, play with commands, and let Tcl teach you Tcl.