Snit -- Snit's Not Incr Tcl

SYNOPSIS

    package require snit 0.93
    ::snit::type name definition
    ::snit::widget name definition
    ::snit::widgetadaptor name definition

OVERVIEW

Snit is yet another pure Tcl object and megawidget system. It's unique among Tcl object systems (so far as I know) in that it's a system based not on inheritance but on delegation. Object systems based on inheritance only allow you to inherit from classes defined using the same system, and that's a shame. In Tcl, an object is anything that acts like an object; it shouldn't matter how the object was implemented. I designed Snit to help me build applications out of the materials at hand; thus, Snit is designed to be able to incorporate and build on any object, whether it's a hand-coded object, a Tk widget, an Incr Tcl object, a BWidget or almost anything else.

This man page is intended to be a reference only; see the accompanying Snit FAQ list for a gentler, more tutorial introduction to Snit concepts.

REFERENCE

Type and Widget Definitions

Snit provides the following commands for defining new types:

snit::type name definition
Defines a new abstract data type called name. If name is not a fully qualified command name, it is assumed to be a name in the namespace in which the snit::type command appears (usually the global namespace). It returns the fully qualified type name.

The type name is then a command which is used to create objects of the new type, along with other activities.

The snit::type definition block may contain the following definitions:

typevariable name ?value?
Defines a type variable with the specified name, and optionally the specified value. Type variables are shared by all instances of the type. This definition can be used to define array variables, but cannot initialize their elements.

typemethod name arglist body
Defines a type method with the specified name, argument list, and body. The variable "type" is automatically defined in the body to the type's fully-qualified name.

The arglist is a normal Tcl argument list and may contain default arguments and the "args" argument; however, it may not contain the argument names "type", "self", "selfns", or "win".

Type variables defined in the type definition are automatically visible in the body of every type method.

typeconstructor body
The type constructor's body is executed once when the type is first defined; it is typically used to initialize array-valued type-variables and to add entries to the Tk option database.

The variable "type" is automatically defined in the body to the type's fully-qualified name.

Type variables defined in the type definition are automatically visible in the body of every type method.

A type may define at most one type constructor.

option namespec ?defaultValue?
Defines an option for instances of this type, and optionally gives it an initial value. (The option's value defaults to "" if no initial value is specified.) An option defined in this way is said to be "locally defined".

The namespec is a list defining the option's name, resource name, and class name, e.g.:

option {-font font Font} {Courier 12}

The option name must begin with a hyphen, and must not contain any upper case letters. The resource name and class name are optional; if not specified, the resource name defaults to the option name, minus the hyphen, and the class name defaults to the resource name with the first letter capitalized. Thus, the following statement is equivalent to the previous example:

option -font {Courier 12}

See the Snit FAQ list for more information about resource and class names.

Options are normally set and retrieved using the standard configure and cget instance methods.

variable name ?value?
Defines an instance variable, a private variable associated with each instance of this type, and optionally its initial value. This definition can be used to define array instance variables, but cannot initialize their elements.

Note that the delegate statement implicitly defines an instance variable for the named component.

method name arglist body
Defines an instance method, a subcommand of each instance of this type, with the specified name, argument list and body. The arglist is a standard Tcl argument list, and may contain default values and the args The arglist is a normal Tcl argument list and may contain default arguments and the "args" argument. In addition, the method is implicitly passed the following arguments as well: "type", which contains the fully-qualified type name; "self", which contains the current instance command name; "selfns", which contains the name of the instance's private namespace; and "win", which contains the original instance name. Consequently, the arglist may not contain the argument names "type", "self", "selfns", or "win".

An instance method defined in this way is said to be "locally defined".

Type and instance variables defined in the type definition are automatically visible in all instance methods. If the type has locally defined options, the "options" array is also visible.

constructor arglist body
The constructor definition specifies a body of code to be executed when a new instance is created.

The arglist is a normal Tcl argument list and may contain default arguments and the "args" argument. As with methods, the arguments "type", "self", "selfns", and "win", are defined implicitly.

If the constructor is not defined, it defaults to this:

              constructor {args} {
                  $self configurelist $args
              }
              

For standard Tk widget behavior (or to achieve the behavior of previous versions of snit) the argument list should be the single name "args", as shown.

destructor body
The destructor is used to code any actions which must take place when an instance of the type is destroyed: typically, the destruction of anything created in the constructor.

As with arguments, the parameters "type", "self", "selfns", and "win", are defined implicitly.

onconfigure name arglist body
Every locally-defined option has an "onconfigure" handler which is called when the option is set to a new value by the "configure" or "configurelist" instance method.

The arglist may contain exactly one argument name. As with methods, the arguments "type", "self", "selfns", and "win", are defined implicitly.

If no explicit onconfigure handler is defined for an option, the handler is defined as follows:

              onconfigure name {value} {
                  set options(name) $value
              }
              
If an explicit onconfigure handler is defined, the options array will be updated with the new value only if the handler so updates it.

oncget name body
Every locally-defined option has an "oncget" handler which is called when the option's value is retrieved. Although there is no explicit argument list, the arguments "type", "self", "selfns", and "win", are defined implicitly, just as they are for methods.

The variables "type", "self", "selfns", and "win" are defined as usual in the handler's body. Whatever the handler returns will be the return value of the call to the cget instance method.

If no explicit oncget handler is defined for an option, the handler is defined as follows:

              oncget name {
                  return $options(name)
              }
              
proc name args body
Defines a new Tcl procedure in the type's namespace. The new proc differs from a normal Tcl proc in that all type variables defined in the type definition are automatically visible.

Although they are not implicitly defined for procs, the argument names "type", "self", "selfns", and "win" should be avoided.

delegate method name to comp
delegate method name to comp as target
delegate method * to comp
delegate method * to comp except exceptions
Delegates one or more instance methods to a component of the object. When a method name is explicitly stated, it will automatically be delegated to the named component as though the method were defined as follows:

              method name {args...} {
                  $comp mymethod args...
              }
              
If desired, the delegated method may target a method with a different name by using the "as" clause; the target may also include arguments to add to the beginning of the argument list.

The form "delegate method *" delegates all unknown method names to the specified comp. The "except" clause can be used to specify a list of exceptions, i.e., method names that will not be so delegated.

A method cannot be both locally defined and delegated.

delegate option namespec to comp
delegate option namespec to comp as target
delegate option * to comp
delegate option * to comp except exceptions
Defines a delegated option; the namespec is defined as for the option statement. When the configure, configurelist, or cget instance method is used to set or retrieve the option's value, the equivalent configure or cget command will be applied to the component as though these onconfigure and oncget handlers were defined, where name is the option name from the namespec:

              onconfigure name {value} {
                  $comp configure name $value
              }
              
              oncget name {
                  return [$comp cget name]
              }
              
If the "as" clause is specified, then the target option name is used in place of name.

The form "delegate option *" delegates all unknown method names to the specified comp. The "except" clause can be used to specify a list of exceptions, i.e., option names that will not be so delegated.

Warning: options can only be delegated to a component if it supports the "configure" and "cget" instance methods.

Note that an option cannot be both locally defined and delegated.

expose comp
expose comp as method
Exposes component comp as a method of the type. In the first form, the method name is comp; in the second form, the method name is method.

This differs from delegation in that it maps an instance method to the component itself instead of to one of the component's methods.

Calling the new instance method is just like calling the component, except that if the method is called with no arguments it returns the component.

snit::widget name definition
This command defines a Snit megawidget type with the specified name. The definition is defined identically to that for snit::type. A snit::widget differs from a snit::type in these ways:

A snit::widget definition can include any of statements allowed in a snit::type definition, and may also include these as well:

widgetclass name
Sets the snit::widget's widget class to name, overriding the default. See the Snit FAQ list for more information about the Tk option database.

hulltype type
Determined the kind of widget used as the snit::widget's hull. The type may be frame (the default) or toplevel.

snit::widgetadaptor name definition
This command defines a Snit megawidget type with the specified name. It differs from snit::widget in that the instance's hull component is not created automatically, but is created in the constructor and installed using the installhull command. Once the hull is installed, its instance command is renamed and replaced as with normal snit::widgets. The original command is again accessible in the instance variable hull.

Note that in general it is not possible to change the widget class of a snit::widgetadaptor's hull widget. See the Snit FAQ list for more information on how snit::widgetadaptors interact with the Tk option database.

snit::typemethod type name arglist body
Defines a new typemethod (or redefines an existing typemethod) for a previously existing type.

snit::method type name arglist body
Defines a new instance method (or redefines an existing instance method) for a previously existing type. Note that delegated instance methods can't be redefined.

The Type Command

A type or widget definition creates a type command, which is used to create instances of the type. The type command this form.

$type typemethod args....
The typemethod can be any of the standard type methods defined in the next section, or any type method defined in the type definition. The subsequent args depend on the specific typemethod chosen.

Standard Type Methods

In addition to any typemethods in the type's definition, all types and widgets will have at least the following method:

$type create name ?option value ...?
Creates a new instance of the type, giving it the specified name and calling the type's constructor.

For snit::types, if name is not a fully-qualified command name, it is assumed to be a name in the namespace in which the call to snit::type appears. The method returns the fully-qualified instance name.

For snit::widgets and snit::widgetadaptors, name must be a valid widget name; the method returns the widget name.

So long as name does not conflict with any defined type method name, the "create" keyword may be omitted.

If the name includes the string "%AUTO%", it will be replaced with the string "$type$counter" where "$type" is the type name and "$counter" is a counter that increments each time "%AUTO%" is used for this type.

By default, any arguments following the name will be a list of option names and their values; however, a type's constructor can specify a different argument list.

$type info typevars ?pattern?
Returns a list of the type's type variables (excluding Snit internal variables); all variable names are fully-qualified.

If pattern is given, it's used as a string match pattern; only names which match the pattern are returned.

$type info instances ?pattern?
Returns a list of the type's instances. For snit::types, it will be a list of fully-qualified instance names; for snit::widgets, it will be a list of Tk widget names.

If pattern is given, it's used as a string match pattern; only names which match the pattern are returned.

$type destroy
Destroys the type's instances, the type's namespace, and the type command itself. This method is defined only for snit::types; snit::widgets use the Tk destroy command instead.

The Instance Command

A Snit type or widget's create type method creates objects of the type; each object has a unique name which is also a Tcl command. This command is used to access the object's methods and data, and has this form:

$object method args...
The method can be any of the standard instance methods defined in the next section, or any instance method defined in the type definition. The subsequent args depend on the specific method chosen.

Standard Instance Methods

In addition to any delegated or locally-defined instance methods in the type's definition, all Snit objects will have at least the following methods:

$object configure ?option? ?value? ...
Assigns new values to one or more options. If called with one argument, an option name, returns a list describing the option, as Tk widgets do; if called with no arguments, returns a list of lists describing all options, as Tk widgets do.

Warning: this information will be available for delegated options only if the component to which they are delegated has a "configure" method that returns this same kind of information.

$object configurelist optionlist
Like configure, but takes one argument, a list of options and their values. It's mostly useful in the type constructor, but can be used anywhere.

$object cget option
Returns the option's value.

$object destroy
Destroys the object, calling the destructor and freeing all related memory.

Note: The "destroy" method isn't defined for snit::widget or snit::widgetadaptor objects; instances of these are destroyed by calling the Tk "destroy" command, just as a normal widget is.

$object info type
Returns the instance's type.

$object info vars ?pattern?
Returns a list of the object's instance variables (excluding Snit internal variables). The names are fully qualified.

If pattern is given, it's used as a string match pattern; only names which match the pattern are returned.

$object info typevars ?pattern?
Returns a list of the object's type's type variables (excluding Snit internal variables). The names are fully qualified.

If pattern is given, it's used as a string match pattern; only names which match the pattern are returned.

$object info options ?pattern?
Returns a list of the object's option names. This always includes local options and explicitly delegated options. If unknown options are delegated as well, and if the component to which they are delegated responds to "$object configure" like Tk widgets do, then the result will include all possible unknown options which could be delegated to the component.

If pattern is given, it's used as a string match pattern; only names which match the pattern are returned.

Note that the return value might be different for different instances of the same type, if component object types can vary from one instance to another.

Commands for use in Object Code

Snit defines the following commands for use in your object code: that is, for use in type methods, instance methods, constructors, destructors, onconfigure handlers, oncget handlers, and procs. They do not reside in the ::snit:: namespace; instead, they are created with the type, and can be used without qualification.
varname name
Given an instance variable name, returns the fully qualified name. Use this if you're passing the variable to some other object, e.g., as a -textvariable to a Tk label widget.

typevarname name
Given an type variable name, returns the fully qualified name. Use this if you're passing the variable to some other object, e.g., as a -textvariable to a Tk label widget.

codename name
Given the name of a proc (but not a type or instance method), returns the fully-qualified command name, suitable for passing as a callback.

from argvName option ?defvalue?
The from command plucks an option value from a list of options and their values, such as is passed into a type's constructor. argvName must be the name of a variable containing such a list; option is the name of the specific option.

from looks for option in the option list. If it is found, it and its value are removed from the list, and the value is returned. If option doesn't appear in the list, then the defvalue is returned. If the option is a normal (undelegated) option, and defvalue is not specified, then the option's default value as specified in the type definition will be returned instead.

variable name
Normally, instance variables are defined in the type definition along with the options, methods, and so forth; such instance variables are automatically visible in all instance-specific code. However, instance code (e.g., method bodies) can declare such variables explicitly using the variable command, if desired; or, instance code can use the variable command to declare instance variables that don't appear in the type definition.

It's generally best to define all instance variables in the type definition, and omit declaring them in methods and so forth.

Note that this is not the same as the standard Tcl "::variable" command.

typevariable name
Normally, type variables are defined in the type definition, along with the instance variables; such type variables are automatically visible in all of the type's code. However, type methods, instance methods and so forth can use typevariable to declare type variables explicitly, if desired; or, they can use typevariable to declare type variables that don't appear in the type definition.

It's generally best to declare all type variables in the type definition, and omit declaring them in methods, type methods, and so forth.

install compName using objType objName args...
Creates a new object and installs it as a component, as described under Components and Delegation.

If this is a snit::type, then the following two commands are equivalent:

       install myComp using myObjType $self.myComp options...
       
       set myComp [myObjType $self.myComp options...]
       
Note that whichever method is used, compName must still be declared in the type definition using variable, or must be referenced in at least one delegate statement.

If this is a snit::widget or snit::widgetadaptor, and if options have been delegated to component compName, then those options will receive default values from the Tk option database. Note that it doesn't matter whether the component to be installed is a widget or not. See the Snit FAQ list for more information about the Tk Option Database.

installhull using widgetType args...
installhull name
The constructor of a snit::widgetadaptor must create a widget to be the object's hull component; the widget is installed as the hull component using this command.

This command has two forms. The first form specifies the widgetType and the args... (that is, the hardcoded option list) to use in creating the hull. Given this form, installhull creates the hull widget, and initializes any options delegated to the hull from the Tk option database.

In the second form, the hull widget has already been created; note that its name must be "$win". In this case, the Tk option database is not queried for any options delegated to the hull. See the Snit FAQ list for more information about snit::widgetadaptors and the Tk option database.

The longer form is preferred; however, the shorter form allows the programmer to adapt a widget created elsewhere, which is sometimes useful. For example, it can be used to adapt a "page" widget created by a BWidgets tabbed notebook or pages manager widget.

mymethod name ?args...?
The mymethod command is used for formatting callback commands to be passed to other objects. It returns a command that when called will invoke method name with the specified arguments, plus of course any arguments added by the caller. In other words, both of the following commands will cause my object's dosomething method to be called when $button is pressed:

       $button configure -command [list $self dosomething myargument]
       
       $button configure -command [mymethod dosomething myargument]
The chief distinction between the two is that the latter form will not break if the creator of my object renames its object command.

Components and Delegation

When an object includes other objects, as when a toolbar contains buttons or a GUI object contains an object that references a database, the included object is called a component. The standard way to handle component objects owned by a Snit object is to assign their names to a instance variable. In the following example, a dog object has a tail object:

snit::type dog {
    variable mytail

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }

    method wag {} {
        $mytail wag
    }
}

snit::type tail {
    option -length 5
    option -partof
    method wag {} { return "Wag, wag, wag."}
}
Because the tail object's name is stored in an instance variable, it's easily accessible in any method.

As of Snit 0.84, the install command provides an alternate way to create and install the component:

snit::type dog {
    variable mytail

    constructor {args} {
        install mytail using tail %AUTO% -partof $self
        $self configurelist $args
    }

    method wag {} {
        $mytail wag
    }
}
For snit::types, the two methods are equivalent; for snit::widgets and snit::widgetadaptors, the "install" command properly initializes delegated options by querying the Tk option database. See the Snit FAQ list for more information. In the above examples, the dog object's "wag" method simply calls the tail component's "wag" method. In OO circles, this is called delegation. Snit provides an easier way to do this, as shown:

snit::type dog {
    delegate method wag to mytail

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }
}
The delegate statement in the type definition implicitly defines the instance variable mytail to hold the component's name; it also defines the dog object's "wag" method, delegating it to the tail component.

If desired, all otherwise unknown methods can be delegated to a specific component:

snit::type dog {
    delegate method * to mytail

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }

    method bark { return "Bark, bark, bark!" }
}
In this case, a dog object will handle its own "bark" method; but "wag" will be passed along to mytail. Any other method, being recognized by neither dog nor tail, will simply raise an error.

Option delegation is similar to method delegation, except for the interactions with the Tk option database; this is described in the Snit FAQ list.

HISTORY

During the course of developing Notebook, my Tcl-based personal notebook application, I found I was writing it as a collection of objects. I wasn't using any particular object-oriented framework; I was just writing objects in pure Tcl following the guidelines in my Guide to Object Commands, along with a few other tricks I'd picked up since. And it was working very well. But on the other hand, it was getting tiresome. Writing objects in pure Tcl is straightforward, once you figure it out, but there's a fair amount of boilerplate code to write for each one, especially if you're trying to create megawidgets or create objects with options, like Tk widgets have..

So that was one thing--tedium is a powerful motivator. But the other thing I noticed is that I wasn't using inheritance at all, and I wasn't missing it. Instead, I was using delegation: objects that created other objects and delegated methods to them.

And I said to myself, "This is getting tedious...there has got to be a better way." And one afternoon, on a whim, I started working on Snit, an object system that works the way Tcl works. Snit doesn't support inheritance, but it's great at delegation, and it makes creating megawidgets easy.

I should add, I'm not particularly down on Incr Tcl. But "Snit's Not Incr Tcl" occurred to me while I was casting about for a name, and I guess there was a certainly inevitability about it.

If you have any comments or suggestions (or bug reports!) don't hesitate to send me e-mail at will@wjduquette.com. In addition, there's now a Snit mailing list; you can find out more about it at the Snit home page, http://www.wjduquette.com/snit. Finally, Snit is now part of tcllib, the standard Tcl library; you can also add bug reports to the tcllib bug database at SourceForge.

CREDITS

Snit has been designed and implemented from the very beginning by William H. Duquette. However, much credit belongs to the following people for using Snit and providing me with valuable feedback: Rolf Ade, Colin McCormack, Jose Nazario, Jeff Godfrey, Maurice Diamanti, Egon Pasztor, David S. Cargo, Tom Krehbiel, Michael Cleverly, Andreas Kupries, Marty Backe, Andy Goth, Jeff Hobbs, and Brian Griffin.


Copyright © 2004, by William H. Duquette. All rights reserved.