Theo Verelst Tcl/Tk Page

Because I feel one can save a lot of progamming and porting effort by usig Tcl/tk, it seems worth having a page on it.

Be sure to try the latest: A embedded Bwise application on a web page (tcl-tk plugin required)!

There are various examples that will grow to more than examples as I proceed on this page, based on Tcl/Tk version 8.0 (or 1), which is freely available from for instance Scriptics, and runs on both Unix, Mac and Pc platforms. To run programs, only the 'wish' graphical interpreter is needed (and maybe some libraries), but the whole package is only a few megabytes in archive form. There is even a Netscape plugin and a combined version with Java, so apart from very good portability (the sources work over these platform usually without a single adaptation), there should be no problem sharing programs with a large audience.

I couldn't say I prefer it over Java, but Java gives you the hassle of compilation, actually without the benefit of generatig stand alone code, and ultimate speed, while tcltk gives you an interpreted language that can compete with Jave in terms of execution speed. Java is C++ like, TclTk is more or less scripting language + LISP +GUI, but has facilities (such as easy symbolic indirections) that can easily give it an object oriented flavour. I guess I like the retro feeling of sitting behind a interpreter remotely resembling another imperative language: back to Basic, or something.

Some of the examples below will make clear why Microsoft isn't to happy having its innovative content tested against the likes of these (why close up Explorer otherwise?).

Some Basic Examles

These examples can be tried out by having a 'wish' interpreter running, and simply pasting the given commands in its command window. Am I promoting Tcl/Tk here? Not realy, I just would like to make clear that it is an environment that deserves serious high level attention.

Assume there is a canvas with name .c available:

canvas .c -bg gray20
pack .c -side bottom -expand y -fill both -anchor s
now make some text:
.c create text 300 200  -text "Block Editor V0.1" \
   -font "helvetica 40" -tags tt -fill purple -anchor c
And do some cheap but flashy animation:
for {set y 0} {$y < 10} {set y [expr $y+1]} 
   {for {set x 0} {$x < 50} {set x [expr $x+1]} 
      {.c itemco tt -font "helvetica $x"; update}}
To save the history of the last session (sort of similar of saving a 'core' image for lisp/smalltalk adepts, a bit more readable though):
history keep 1000
set f [open "last.his" w]
puts $f [history]
close $f
I'll dig up some of the socket stuff I did some time ago, to make two Tcl/Tk interpreters talk over the internet. Sort of a unix 'talk' equivalent in a few lines of code. Stay tuned.

The simple version of setting up a connection, in this case on two interpreters running on the same machine is:

****TCL SHELL 1:***
set s [socket -server  fs 1235]
proc fs {fd host  p} { puts $fd;  
   fileevent $fd readable  "gets $fd l; puts stdout \$l"  }

***TCL SHELL 2:***
set s2 [socket [info hostname] 1235]
proc s2 {s} {puts sock4 $s; flush sock4}
After which we can send a message from interpreter 2 to 1 by simply using:
***SHELL 2:***
s2 "Does this message appear on the other end automatically?"
The same string should appear in the other window without further programming.
Of course we have Tk too, so lets do some UI here, if we replace the above procedure fs by:
proc fs {fd host  p} { puts $fd;  fileevent $fd readable  "gets $fd l; puts stdout \$l;
    .t insert end \$l; .t insert end \\n; .t see end"  }

(first close the current connection, or use another in parallel), a text window will capture the messages. The other end could be made to have a text entry field by using:
entry .e -textvar et  ; pack .e
bind .e <Return> {global et; s2 $et; .e sel range 0 end}
to type in any line (and be ready for the next) that appears on the bottom of the text window on the other interpreter, which could be on any ohter internet node and machine type...

Start of a Interactive Block Diagram Editor

I've left in some of the old text, check further on for a version 0.5 of a working graph editor.

Some history

Did this in an hour or so after having had a long break of not using Tcl/Tk, it is a set up for making an interactive block diagram, with graphical representation of active elements and wires that connect them up. The good part about doing that in Tcl/Tk is that it is fairly easy to give the interface an added value over using an already available net editor, by giving elements of the graph interactive controls, such as menus, output text windows (in the network), sliders, input fields, graphs, images, etc.

A simple block generating routine, and its use to create two named blocks:

proc crb {x y w h name} {eval .c create rect $x $y [expr $x+$w] 
   [expr $y+$h] -tags "{$name crb block}" -fill yellow 
   -outline darkblue; 
   eval .c create text  $x [expr $y+$h] -anchor nw 
   -text $name -fill darkblue -tags "{$name crb label}" }
crb 100 50 50 50 t1
crb 160 50 50 50 t2
Note the use of tags, they make it easy to manipulate multiple items, such as deleting a block by referencing its global name:
proc delb {name} {foreach i [.c find withtag $name] [.c del $i] }
delb t1
delb all
or
.c move t2 100 50
to move both text and block 't2' around on the canvas.

Some things have to be hand made, such as a routine to find all canvas items with two matching tag names (I'll do a general list based set Union operator soon):

proc find2l {l1 l2} {foreach i [.c find withtag $l1] 
   {if {[lsearch [.c itemcget $i -tags]  $l2 ] >= 0} {return $i}} }

A first version

Skipping some of the explanation, here is the tcl/tk code of version 0.5 of a working block editor prototype (I have more prepared already, will add later), looking like this:

The blocks can be moved around by simpy picking the up, and the wires stay connected on the scrollable canvas. Double click blocks to select or deselect them, touch pins to select them (they'll turn green). When two pins are connected, use the wire button to put a wire in between. Blocks are created by using the block or scope button, and dragging them out of the upper left corner. You'll also need this file to make the code run, simply put it in the same directory with bwise.tcl, install bwise.tcl for using bin/wish.exe from the Tcl/Tk distribution, double click bwise, and the application starts.

Al this with under 10 KILO bytes of code...
(I have more prepared, such as changing names and properties dynamically.)

Drum Track Editor / Simple Sequencer

I did some prelim stuff I'll include here. The source code for this drum track generator generates generic blocks for bwise:

I've been using sockets again to link a bwise window with a simple tcl/tk text window containing the (ascii) commands for generating drum tracks with the latest (gnu C) drum sample program which read commands of the form 'instrument start_time lenght amplitude' from its stdin.

This graphical representation can easily be transformed in the required command stream, enabling all kinds of sample and interactive processing...

A Bwise tcl/tk shell block

To make more use of the (partly future) possibilities of the Bwise programming environment, i've implemented a tcl/tk interpreting shell as a block that can be created on the main canvas by pressing the paper symbol button. Multiple instances are allowed, and they basically all work as the main console window, except that the results of puts commands do not appear in the small history window, but the command return values do.
When a command has been typed, the previous command is selected, so that it disappears whena new character is typed, or can be edited by pressing an arrow key. The history window is a scrollable, selectable, and in fact editable text window (drag mouse to select, press control-c to copy, and control-v to past in the command line).

A command is exectuted py pressing return, or by Activating te Eval button.

Here is an example connected Bwise diagram with one shell:

Inspecting graphical interface structures

The following two procedures generate a 'tree'-like representation of the current hierarchical interface structure, by using LISP like recursive list processing:
% proc rinf {{parent .} {do {} }} {foreach i [winfo children $parent] \
	   {if {$do != {}} {$do $i} {puts $i; }; rinf $i  $do }}
% proc pp {i} {for {set j 1} {$j<[llength [ split $i . ]]} {incr j} \
           {puts -nonewline "   "} ;puts "$i"}
% rinf . pp
   .fb
      .fb.bnewb
      .fb.quit
      .fb.p
      .fb.bwire
      .fb.bcdrum
      .fb.bcscope
   .mw
      .mw.hscroll
      .mw.vscroll
      .mw.c
         .mw.c.shell0
            .mw.c.shell0.e
            .mw.c.shell0.b
            .mw.c.shell0.t
         .mw.c.scope0
            .mw.c.scope0.c
            .mw.c.scope0.s
% 
Where mw.c is the canvas containing variuous bwise blocks, of which .mw.c.shell0 and .mw.scope0 are subwindows.

It is not too hard to gather this data in a list window, and call up the configuration data for an item in an associated text window, I'll gather the code up from my history file as I have time. Unfortunately, it is not possible to blindly read back the configuration data by automatically generating a configure command with the latest options, because some options cannot be set after they hae been once determined (such as the -class option). This makes interactive editing by using the rather short code to do the above a bit less attractive, some list of excluded options would be needed.

It should be possible to gather the pack data as well, so that an existing graphica structure can be captured in tcl/tk that generates it quite straightforwardly.

Timed events

In tcl, it is possible to schedule events for later activation, sort of like Unix 'at', by using after. In the following code, I use this command and the concept of a semaphore to do initiate a known number of equally time spaced events: (the command shell could look like this:)
% proc rupdate {dt {pro {}}} { global rupdatesema; set rupdatesema [expr $rupdatesema -1] ; 
    if {$rupdatesema > 0} {if {$pro != {}} {pro};after $dt "rupdate $dt $pro" } }
% proc pro {} {global rupdatesema; puts $rupdatesema}
% set rupdatesema 10
% rupdate 1000 pro
9
after#1
8
7
6
5
% after cancel [after info]
% puts $rupdatesema
5
%
A list of the scheduled events can be obtained by the info command, and fed back to cancel future events.

The procedure pro could also contain a command such as

$mc create text 200 200 -text "10" -tag tv -fill purple \
   -tag tv -font "helvetica 80"
proc pro {} {global rupdatesema mc; $mc itemco tv -text $rupdatesema}
set rupdatesema 10
rupdate 1000 pro
to create a huge countdown counter, assuming the bwise.tcl file was source-d previously.

Saving and more facilities under Bwise

The latest version of Bwise can save and load canvasses, albeit without the scope and shell types (they wont reload), with standard file interface. Note that all changes and properties of the canvas (apart from subwindows) are saved exactly as they are, incuding all user defined items. That is you can include everything you want on the canvas, and it will be saved without the need for sav routines per object type or something. All connections are done through tags, which are standard tcl/tk graphical object properties, so all that is needed is to make all routines work on the basis of the tag set of each graphical object. Note that the output file is simply tcl/tk code, so it can be read into any other tcl/tk canvas environment easily.

NOTE: There are various conventions concerning the tags of Bwise objects, which I'll summarize in another place, but which roughly mean that the first tag of each object is its ('instance', or 'actual') name, and the second the group (or 'class' or 'type') ot belongs to. Wires have their end points in terms of connectivity defined by the tag 3 through 6, which are pairs of {blockname pinname}, and there are various tags which have special meaning, such as selection0, which is assigned to all blocks which are double clicked on. The order of the first set of tags is essential, and should not be changed. Blocks are grouped together (e.g. for motion) by their common first tag.

More editing facilities have been added, such as graphically adding wires. Select two pins, by clicking on them to make them green, and press the wire button. The pins will deselect again, and a wire is drawn. Click on a wire to toggle the selection of the pins it is connected to, press Wire again to remove the wire, while the pins stay selected (so the wire could be redrawn by pressing Wire again).

Blocks can be deleted by selecting a set of them by double clicking on them, giving them a red background, and pressing Del Sel (delete selection). They are now irrecoverably deleted.

To clear the canvas, simply make an empty canvas file by starting a Bwise, and immedeately pressing save (e.g. to empty.tcl), and loading this file when desired. The load is a destructive load, and no confirmation will be asked for!

A simple example bwise canvas

Here is a picture of the example represented by the code adder.tcl which can be read in Bwise:

The blocks shown above were created on the command shell. To run Bwise with a command shell, and possibly combined with more code (your own ?),first start up 'wish', and use 'source bwise.tcl' from the right directory to start up. For source code development, it is usefull to note that the turnaround time of changing something in for isntance the Bwise code, and trying the results is a matter a seconds: save the new code, source it again, and you're set. This is because Bwise is made to be reloadable, and can even be quit without destroying the root window without when run with a console , so other tcl/tk code may continue to be active. It is convenient to keep a long history during development (e.g. use history keep 1000, and loading the latest (edited) source code is then simply a matter of !so or something, which works very fast because Bwise is small, and requires little installation.

After having started up with a shell, the adder shown above was created by:

newblock adder0 0 0 30 80 {ina inb inc outs outc}
newblock buffer0 0 0 30 60 {in out clock}
newblock buffer1 0 0 30 50 {in out clock}
newblock buffer2 0 0 30 50 {in out clock}
newblock ground 0 0 20 20 {gound}
newblock connector0 0 0 10 80 {in1 in2 out clock}
Of course, all blocks have to be dragged into place, simply by clicking on them and moving them. The connections were made by selecting the pins and drawing the right wires (see below for an alternative, using the Bwise procedure 'connect')

Calling the function gen_netlist will generate a type of net-list, where each connection in the canvas is stated on a new line, in a form that can be directly read by Bwise.


gen_netlist
connect wire5 adder0 ina buffer0 out 
connect wire7 adder0 inb buffer1 out 
connect wire9 adder0 outs buffer2 in 
connect wire12 adder0 inc ground gound 
connect wire13 buffer0 in connector0 in1 
connect wire14 buffer1 in connector0 in2 
connect wire15 buffer2 out connector0 out 
connect wire16 buffer0 clock connector0 clock 
connect wire17 buffer1 clock connector0 clock 
connect wire18 buffer2 clock connector0 clock 
After storing and deleting all the wires, they can be regenerated by using the this data:

set wires [gen_netlist]
$mc del [tag_and {wire}]
eval $wires
The Bwise procedure tag_and {list} returns al list of all graphical indices from the main Bwise canvas ($mc), that have all tags in the supplied input list: an 'and' function on the set of tags.

Of course the netlist can easily be used (and adapted) for other purposes.