obj

wdb --- My first experiences with OO extensions in Tcl/Tk was Itcl which I used intensively on programming my CMS One Hand Content. This extension worked mostly as expected – with a few pitfalls and one thing I found annoying: options were mapped to internal variables of the objects. More than once I ran in danger of name collision inside method definitions.

Then, I found Snit and really enjoyed it. Especially, options are mapped to key/value pairs of an array. No more danger of name collision! — Instead of heritage, components are installed where methods and options can be delegated to: really great! — But, I found the hint that performance disappoints if there are many once-used local objects. But that was exactly what I wanted in my current project on computer-generated cartoons [L1 ].

So, I made obj — just another OO system. It does not seamlessly fit into Tk's object approach, and it is far from being complete. But it is minimalistic, and it is (at least intended to be) quite fast. I have put it to a section of half-bakery [L2 ]. Documentation in zip file besides package file.

Update — code to find on my homepage


Lars H: I'd say it's generally a mistake to hide documentation inside an archive like that. For the casual "surfer", it comes across as "don't consider this package unless you're really interested", which is usually the opposite of what one wants as a package author.


wdb Ok, here a copy of the tutorial:


This tutorial describes obj, a minimalistic object system for Tcl.

As I made it for my project on computer-generated cartoons, the examples in this tutorial deal of a simple system of morphing lines. We create a class line and a class morph which interpolates two lines.

The code lines in this tutorial show the dialog of interactive shell tclsh. The percent sign % is the prompt. Completed lines without leading prompt are responses of Tcl as you are used by the shell.

Loading package

Either you have just copied the file into some directory, then input:

 % source obj-0.1.tm
 %

Or, if you have put it to your package path, then input:

 % package require obj
 0.1
 %

Class definition

First, we create our class line:

 % obj::class line
 class line
 %

Options

Every line has coords for the start and end point stored in option -coords. The default value shall be {10 10 100 10}:

 % obj::configure line -coords {10 10 100 10}
 class line has option -coords with default {10 10 100 10}
 %

First instance

Now is the time for all brave ... err ... well, we try our first object creation:

 % set line1 [obj::new line -coords {10 10 10 100}]
 ::obj::inst::3
 %

No smoke at the keyboard. Phew. Now let us check if coordinates have value as expected:

 % $line1 cget -coords
 10 10 10 100
 %

Defining methods

Our line shall be able to appear and disappear on an arbitrary canvas widget. (Note that inside methods, the variable $self refers to the object itself):

 % obj::method line appear canvas {
  $canvas delete $self
  $canvas create line [$self cget -coords] -tags $self
 }
 method line appear
 %
 % obj::method line disappear canvas {
  $canvas delete $self
 }
 method line disappear 
 %

Now try it out:

 % package require Tk
 8.5.1
 % pack [canvas .c] -expand yes -fill both
 % $line1 appear .c
 1
 %

As expected: a vertical line appears.

Trust is cool. Control is hot: Validating options

To avoid inappropriate values for option -coords, we always validate them on change:

 % obj::validatemethod line -coords val {
  foreach c $val {
    if {![string is double -strict $c]} then {
      return -code error\
        [list $self expects for -coords only numbers\
          but received $c]!
    }
  }
 }
 validatemethod line -coords
 %

If we happen to try to configure our line1 with inappropriate values for -coords, an error is raised:

 % $line1 configure -coords {x 2 3 4}
 ::obj::inst::3 expects for -coords only numbers but received x!
 %

Defining a morphing method

This method destructively moves object's coords towards another line object:

 % obj::method line morph {other f} {
  set coords {}
  foreach {x0 y0} [$self cget -coords] {x1 y1} [$other cget -coords] {
    lappend coords [expr {$x0+($x1-$x0)*$f}] [expr {$y0+($y1-$y0)*$f}]
  }
  $self configure -coords $coords
 }
 method line morph
 %

Next class: morph

Now, the basics are done. We start our morphing class

 % obj::class morph
 class morph
 %

An instance of morph needs a starting line, an ending line, a resulting line, coords for each of them, and a state:

 % obj::configure morph -coords {10 10 10 100}
 class morph has option -coords with default {10 10 10 100}
 %
 % obj::configure morph -startcoords {10 10 10 100}
 class morph has option -startcoords with default {10 10 10 100}
 %
 % obj::configure morph -endcoords {100 10 100 100}
 class morph has option -endcoords with default {100 10 100 100}
 %
 % obj::configure morph -state 0.0
 class morph has option -state with default 0.0
 %

Components

Objects can have private data. If one private date is the name of another object, then we can treat this as a component. The constructor of morph shall install components start, end, line:

 % obj::constructor morph {x0 y0 x1 y1 x2 y2 x3 y3} {
  $self private start [new line -coords "$x0 $y0 $x1 $y1"]
  $self private end [new line -coords "$x2 $y2 $x3 $y3"]
  $self private line [new line]
 }
 constructor morph
 %

Now, let us test it:

 % set m [obj::new morph 10 10 10 100 50 50 30 100]
 ::obj::inst::5
 %

We can always see all names of private data of the instance:

 % $m private
 start end line
 %

See the value of private curve:

 % $m private line
 ::obj::inst::8
 %

We can invoke any defined method of private line:

 % $m component line cget -coords
 10 10 100 10
 %

Delegation

The methods appear, disappear are delegated to component line:

 % obj::delegate method appear morph line
 class morph delegates method appear to component line.
 %
 % obj::delegate method disappear morph line
 class morph delegates method disappear to component line.
 %

The option -coords relates to component line:

 % obj::delegate option -coords morph line
 class morph delegates option -coords to component line.
 %

Delegation with different target names

The option -startcoords relates to option -coords of component start, the same for end, so delegate options with explicitly set name of target options:

 % obj::delegate option -startcoords morph start -coords
 class morph delegates option -startcoords to component start.
 %
 % obj::delegate option -endcoords morph end -coords
 class morph delegates option -endcoords to component end.
 %

Read-only options

Option -coords is set by configuremethod -state (see below). Setting it directly should be forbidden:

 % obj::read-only morph -coords
 option -coords of class morph is read-only.
 %

Actions triggered by configuremethod

Our morphing object shall change its component line depending of option -state, thus we define the appropriate configuremethod:

 % obj::configuremethod morph -state s {
  $self component line configure -coords\
    [$self component start cget -coords]
  $self component line morph [$self private end] $s
 }
 configuremethod morph -state
 %

Now, test it:

 % $m configure -state 0.5
 % $m appear .c
 10
 %

The morphed component line appears on canvas .c.

To watch the state of $m continuously, we create a scale which interactively sets the state:

 % scale .c.s -orient horizontal
 .c.s
 % place .c.s -anchor sw -relwidth 1.0 -rely 1.0
 % .c.s configure -command [list apply {{obj num} {
  $obj configure -state [expr {$num / 100.0}]
  $obj appear .c
 }} $m]
 %

What remains? Just fine-tuning as you wish. Have fun with object-oriented programming!