Tcl Library Source Code

Documentation
Login


[ Main Table Of Contents | Table Of Contents | Keyword Index | Categories | Modules | Applications ]

NAME

snit - Snit's Not Incr Tcl

Table Of Contents

SYNOPSIS

package require Tcl 8.5 9
package require snit ?2.3.3?

snit::type name definition
typevariable name ?-array? ?value?
typemethod name arglist body
typeconstructor body
variable name ?-array? ?value?
method name arglist body
option namespec ?defaultValue?
option namespec ?options...?
constructor arglist body
destructor body
proc name args body
delegate method name to comp ?as target?
delegate method name ?to comp? using pattern
delegate method * ?to comp? ?using pattern? ?except exceptions?
delegate option namespec to comp
delegate option namespec to comp as target
delegate option * to comp
delegate option * to comp except exceptions
component comp ?-public method? ?-inherit flag?
delegate typemethod name to comp ?as target?
delegate typemethod name ?to comp? using pattern
delegate typemethod * ?to comp? ?using pattern? ?except exceptions?
typecomponent comp ?-public typemethod? ?-inherit flag?
pragma ?options...?
expose comp
expose comp as method
onconfigure name arglist body
oncget name body
snit::widget name definition
widgetclass name
hulltype type
snit::widgetadaptor name definition
snit::typemethod type name arglist body
snit::method type name arglist body
snit::macro name arglist body
snit::compile which type body
$type typemethod args...
$type create name ?option value ...?
$type info typevars ?pattern?
$type info typemethods ?pattern?
$type info args method
$type info body method
$type info default method aname varname
$type info instances ?pattern?
$type destroy
$object method args...
$object configure ?option? ?value? ...
$object configurelist optionlist
$object cget option
$object destroy
$object info type
$object info vars ?pattern?
$object info typevars ?pattern?
$object info typemethods ?pattern?
$object info options ?pattern?
$object info methods ?pattern?
$object info args method
$object info body method
$object info default method aname varname
mymethod name ?args...?
mytypemethod name ?args...?
myproc name ?args...?
myvar name
mytypevar name
from argvName option ?defvalue?
install compName using objType objName args...
installhull using widgetType args...
installhull name
variable name
typevariable name
varname name
typevarname name
codename name
snit::boolean validate ?value?
snit::boolean name
snit::double validate ?value?
snit::double name ?option value...?
snit::enum validate ?value?
snit::enum name ?option value...?
snit::fpixels validate ?value?
snit::fpixels name ?option value...?
snit::integer validate ?value?
snit::integer name ?option value...?
snit::listtype validate ?value?
snit::listtype name ?option value...?
snit::pixels validate ?value?
snit::pixels name ?option value...?
snit::stringtype validate ?value?
snit::stringtype name ?option value...?
snit::window validate ?value?
snit::window name

DESCRIPTION

Snit is a pure Tcl object and megawidget system. It's unique among Tcl object systems in that it's based not on inheritance but on delegation. Object systems based on inheritance only allow you to inherit from classes defined using the same system, which is limiting. In Tcl, an object is anything that acts like an object; it shouldn't matter how the object was implemented. Snit is intended to help you 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 snitfaq for a gentler, more tutorial introduction to Snit concepts.

SNIT VERSIONS

This man page covers both Snit 2.2 and Snit 1.3. The primary difference between the two versions is simply that Snit 2.2 contains speed optimizations based on new features of Tcl 8.5; Snit 1.3 supports all of Tcl 8.3, 8.4 and Tcl 8.5. There are a few minor inconsistencies; they are flagged in the body of the man page with the label "Snit 1.x Incompatibility"; they are also discussed in the snitfaq.

REFERENCE

Type and Widget Definitions

Snit provides the following commands for defining new types:

The Type Command

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

Standard Type Methods

In addition to any type methods in the type's definition, all type and widget commands will usually have at least the following subcommands:

The Instance Command

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

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 subcommands:

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.

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 declare them using component, which creates a component instance variable. In the following example, a dog object has a tail object:

snit::type dog {
    component 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.

The install command provides an alternate way to create and install the component:

snit::type dog {
    component mytail

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

    method wag {} {
        $mytail wag
    }
}

For snit::type__s, the two methods are equivalent; for __snit::widget__s and __snit::widgetadaptor__s, the __install command properly initializes the widget's options by querying The Tk Option Database.

In the above examples, the dog object's wag method simply calls the tail component's wag method. In OO jargon, this is called delegation. Snit provides an easier way to do this:

snit::type dog {
    delegate method wag to mytail

    constructor {args} {
        install mytail using 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 (though it's good form to use component to declare it explicitly); it also defines the dog object's wag method, delegating it to the mytail 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 Tk Option Database.

Type Components and Delegation

The relationship between type components and instance components is identical to that between type variables and instance variables, and that between type methods and instance methods. Just as an instance component is an instance variable that holds the name of a command, so a type component is a type variable that holds the name of a command. In essence, a type component is a component that's shared by every instance of the type.

Just as delegate method can be used to delegate methods to instance components, as described in Components and Delegation, so delegate typemethod can be used to delegate type methods to type components.

Note also that as of Snit 0.95 delegate method can delegate methods to both instance components and type components.

The Tk Option Database

This section describes how Snit interacts with the Tk option database, and assumes the reader has a working knowledge of the option database and its uses. The book Practical Programming in Tcl and Tk by Welch et al has a good introduction to the option database, as does Effective Tcl/Tk Programming.

Snit is implemented so that most of the time it will simply do the right thing with respect to the option database, provided that the widget developer does the right thing by Snit. The body of this section goes into great deal about what Snit requires. The following is a brief statement of the requirements, for reference.

The interaction of Tk widgets with the option database is a complex thing; the interaction of Snit with the option database is even more so, and repays attention to detail.

Setting the widget class: Every Tk widget has a widget class. For Tk widgets, the widget class name is the just the widget type name with an initial capital letter, e.g., the widget class for button widgets is "Button".

Similarly, the widget class of a snit::widget defaults to the unqualified type name with the first letter capitalized. For example, the widget class of

snit::widget ::mylibrary::scrolledText { ... }

is "ScrolledText". The widget class can also be set explicitly using the widgetclass statement within the snit::widget definition.

Any widget can be used as the hulltype provided that it supports the -class option for changing its widget class name. See the discussion of the hulltype command, above. The user may pass -class to the widget at instantion.

The widget class of a snit::widgetadaptor is just the widget class of its hull widget; this cannot be changed unless the hull widget supports -class, in which case it will usually make more sense to use snit::widget rather than snit::widgetadaptor.

Setting option resource names and classes: In Tk, every option has three names: the option name, the resource name, and the class name. The option name begins with a hyphen and is all lowercase; it's used when creating widgets, and with the configure and cget commands.

The resource and class names are used to initialize option default values by querying the Tk option database. The resource name is usually just the option name minus the hyphen, but may contain uppercase letters at word boundaries; the class name is usually just the resource name with an initial capital, but not always. For example, here are the option, resource, and class names for several text widget options:

-background         background         Background
-borderwidth        borderWidth        BorderWidth
-insertborderwidth  insertBorderWidth  BorderWidth
-padx               padX               Pad

As is easily seen, sometimes the resource and class names can be inferred from the option name, but not always.

Snit options also have a resource name and a class name. By default, these names follow the rule given above: the resource name is the option name without the hyphen, and the class name is the resource name with an initial capital. This is true for both locally-defined options and explicitly delegated options:

    snit::widget mywidget {
        option -background
        delegate option -borderwidth to hull
        delegate option * to text
	# ...
    }

In this case, the widget class name is "Mywidget". The widget has the following options: -background, which is locally defined, and -borderwidth, which is explicitly delegated; all other widgets are delegated to a component called "text", which is probably a Tk text widget. If so, mywidget has all the same options as a text widget. The option, resource, and class names are as follows:

-background  background  Background
-borderwidth borderwidth Borderwidth
-padx        padX        Pad

Note that the locally defined option, -background, happens to have the same three names as the standard Tk -background option; and -pad, which is delegated implicitly to the text component, has the same three names for mywidget as it does for the text widget. -borderwidth, on the other hand, has different resource and class names than usual, because the internal word "width" isn't capitalized. For consistency, it should be; this is done as follows:

    snit::widget mywidget {
	option -background
	delegate option {-borderwidth borderWidth} to hull
	delegate option * to text
	# ...
    }

The class name will default to "BorderWidth", as expected.

Suppose, however, that mywidget also delegated -padx and -pady to the hull. In this case, both the resource name and the class name must be specified explicitly:

    snit::widget mywidget {
	option -background
	delegate option {-borderwidth borderWidth} to hull
	delegate option {-padx padX Pad} to hull
	delegate option {-pady padY Pad} to hull
	delegate option * to text
	# ...
    }

Querying the option database: If you set your widgetclass and option names as described above, Snit will query the option database when each instance is created, and will generally do the right thing when it comes to querying the option database. The remainder of this section goes into the gory details.

Initializing locally defined options: When an instance of a snit::widget is created, its locally defined options are initialized as follows: each option's resource and class names are used to query the Tk option database. If the result is non-empty, it is used as the option's default; otherwise, the default hardcoded in the type definition is used. In either case, the default can be overridden by the caller. For example,

    option add *Mywidget.texture pebbled

    snit::widget mywidget {
	option -texture smooth
	# ...
    }

    mywidget .mywidget -texture greasy

Here, -texture would normally default to "smooth", but because of the entry added to the option database it defaults to "pebbled". However, the caller has explicitly overridden the default, and so the new widget will be "greasy".

Initializing options delegated to the hull: A snit::widget's hull is a widget, and given that its class has been set it is expected to query the option database for itself. The only exception concerns options that are delegated to it with a different name. Consider the following code:

    option add *Mywidget.borderWidth 5
    option add *Mywidget.relief sunken
    option add *Mywidget.hullbackground red
    option add *Mywidget.background green

    snit::widget mywidget {
	delegate option -borderwidth to hull
	delegate option -hullbackground to hull as -background
	delegate option * to hull
	# ...
    }

    mywidget .mywidget

    set A [.mywidget cget -relief]
    set B [.mywidget cget -hullbackground]
    set C [.mywidget cget -background]
    set D [.mywidget cget -borderwidth]

The question is, what are the values of variables A, B, C and D?

The value of A is "sunken". The hull is a Tk frame that has been given the widget class "Mywidget"; it will automatically query the option database and pick up this value. Since the -relief option is implicitly delegated to the hull, Snit takes no action.

The value of B is "red". The hull will automatically pick up the value "green" for its -background option, just as it picked up the -relief value. However, Snit knows that -hullbackground is mapped to the hull's -background option; hence, it queries the option database for -hullbackground and gets "red" and updates the hull accordingly.

The value of C is also "red", because -background is implicitly delegated to the hull; thus, retrieving it is the same as retrieving -hullbackground. Note that this case is unusual; in practice, -background would probably be explicitly delegated to some other component.

The value of D is "5", but not for the reason you think. Note that as it is defined above, the resource name for -borderwidth defaults to "borderwidth", whereas the option database entry is "borderWidth". As with -relief, the hull picks up its own -borderwidth option before Snit does anything. Because the option is delegated under its own name, Snit assumes that the correct thing has happened, and doesn't worry about it any further.

For snit::widgetadaptor__s, the case is somewhat altered. Widget adaptors retain the widget class of their hull, and the hull is not created automatically by Snit. Instead, the __snit::widgetadaptor must call installhull in its constructor. The normal way to do this is as follows:

    snit::widgetadaptor mywidget {
	# ...
	constructor {args} {
	    # ...
	    installhull using text -foreground white
	    #
	}
	#...
    }

In this case, the installhull command will create the hull using a command like this:

set hull [text $win -foreground white]

The hull is a text widget, so its widget class is "Text". Just as with snit::widget hulls, Snit assumes that it will pick up all of its normal option values automatically; options delegated from a different name are initialized from the option database in the same way.

Initializing options delegated to other components: Non-hull components are matched against the option database in two ways. First, a component widget remains a widget still, and therefore is initialized from the option database in the usual way. Second, the option database is queried for all options delegated to the component, and the component is initialized accordingly--provided that the install command is used to create it.

Before option database support was added to Snit, the usual way to create a component was to simply create it in the constructor and assign its command name to the component variable:

    snit::widget mywidget {
	delegate option -background to myComp

	constructor {args} {
	    set myComp [text $win.text -foreground black]
	}
    }

The drawback of this method is that Snit has no opportunity to initialize the component properly. Hence, the following approach is now used:

    snit::widget mywidget {
	delegate option -background to myComp

	constructor {args} {
	    install myComp using text $win.text -foreground black
	}
    }

The install command does the following:

Non-widget components: The option database is never queried for snit::type__s, since it can only be queried given a Tk widget name. However, __snit::widget__s can have non-widget components. And if options are delegated to those components, and if the __install command is used to install those components, then they will be initialized from the option database just as widget components are.

Macros and Meta-programming

The snit::macro command enables a certain amount of meta-programming with Snit classes. For example, suppose you like to define properties: instance variables that have set/get methods. Your code might look like this:

snit::type dog {
    variable mood happy

    method getmood {} {
        return $mood
    }

    method setmood {newmood} {
        set mood $newmood
    }
}

That's nine lines of text per property. Or, you could define the following snit::macro:

snit::macro property {name initValue} {
    variable $name $initValue

    method get$name {} "return $name"

    method set$name {value} "set $name \$value"
}

Note that a snit::macro is just a normal Tcl proc defined in the slave interpreter used to compile type and widget definitions; as a result, it has access to all the commands used to define types and widgets.

Given this new macro, you can define a property in one line of code:

snit::type dog {
    property mood happy
}

Within a macro, the commands variable and proc refer to the Snit type-definition commands, not the standard Tcl commands. To get the standard Tcl commands, use _variable and _proc.

Because a single slave interpreter is used for compiling all Snit types and widgets in the application, there's the possibility of macro name collisions. If you're writing a reuseable package using Snit, and you use some __snit::macro__s, define them in your package namespace:

snit::macro mypkg::property {name initValue} { ... }

snit::type dog {
    mypkg::property mood happy
}

This leaves the global namespace open for application authors.

Validation Types

A validation type is an object that can be used to validate Tcl values of a particular kind. For example, snit::integer is used to validate that a Tcl value is an integer.

Every validation type has a validate method which is used to do the validation. This method must take a single argument, the value to be validated; further, it must do nothing if the value is valid, but throw an error if the value is invalid:

snit::integer validate 5     ;# Does nothing
snit::integer validate 5.0   ;# Throws an error (not an integer!)

The validate method will always return the validated value on success, and throw the -errorcode INVALID on error.

Snit defines a family of validation types, all of which are implemented as snit::type's. They can be used as is; in addition, their instances serve as parameterized subtypes. For example, a probability is a number between 0.0 and 1.0 inclusive:

snit::double probability -min 0.0 -max 1.0

The example above creates an instance of snit::double--a validation subtype--called probability, which can be used to validate probability values:

probability validate 0.5   ;# Does nothing
probability validate 7.9   ;# Throws an error

Validation subtypes can be defined explicitly, as in the above example; when a locally-defined option's -type is specified, they may also be created on the fly:

snit::enum ::dog::breed -values {mutt retriever sheepdog}

snit::type dog {
    # Define subtypes on the fly...
    option -breed -type {
        snit::enum -values {mutt retriever sheepdog}
    }

    # Or use predefined subtypes...
    option -breed -type ::dog::breed
}

Any object that has a validate method with the semantics described above can be used as a validation type; see Defining Validation Types for information on how to define new ones.

Snit defines the following validation types:

Defining Validation Types

There are three ways to define a new validation type: as a subtype of one of Snit's validation types, as a validation type command, and as a full-fledged validation type similar to those provided by Snit. Defining subtypes of Snit's validation types is described above, under Validation Types.

The next simplest way to create a new validation type is as a validation type command. A validation type is simply an object that has a validate method; the validate method must take one argument, a value, return the value if it is valid, and throw an error with -errorcode INVALID if the value is invalid. This can be done with a simple proc. For example, the snit::boolean validate type could have been implemented like this:

proc ::snit::boolean {"validate" value} {
    if {![string is boolean -strict $value]} {
        return -code error -errorcode INVALID  "invalid boolean \"$value\", should be one of: 1, 0, ..."
    }

    return $value
}

A validation type defined in this way cannot be subtyped, of course; but for many applications this will be sufficient.

Finally, one can define a full-fledged, subtype-able validation type as a snit::type. Here's a skeleton to get you started:

snit::type myinteger {
    # First, define any options you'd like to use to define
    # subtypes.  Give them defaults such that they won't take
    # effect if they aren't used, and marked them "read-only".
    # After all, you shouldn't be changing their values after
    # a subtype is defined.
    #
    # For example:

    option -min -default "" -readonly 1
    option -max -default "" -readonly 1

    # Next, define a "validate" type method which should do the
    # validation in the basic case.  This will allow the
    # type command to be used as a validation type.

    typemethod validate {value} {
        if {![string is integer -strict $value]} {
            return -code error -errorcode INVALID  "invalid value \"$value\", expected integer"
        }

        return $value
    }

    # Next, the constructor should validate the subtype options,
    # if any.  Since they are all readonly, we don't need to worry
    # about validating the options on change.

    constructor {args} {
        # FIRST, get the options
        $self configurelist $args

        # NEXT, validate them.

        # I'll leave this to your imagination.
    }

    # Next, define a "validate" instance method; its job is to
    # validate values for subtypes.

    method validate {value} {
        # First, call the type method to do the basic validation.
        $type validate $value

        # Now we know it's a valid integer.

        if {("" != $options(-min) && $value < $options(-min))  ||
            ("" != $options(-max) && $value > $options(-max))} {
            # It's out of range; format a detailed message about
            # the error, and throw it.

            set msg "...."

            return -code error -errorcode INVALID $msg
        }

        # Otherwise, if it's valid just return it.
        return $valid
    }
}

And now you have a type that can be subtyped.

The file "validate.tcl" in the Snit distribution defines all of Snit's validation types; you can find the complete implementation for snit::integer and the other types there, to use as examples for your own types.

CAVEATS

If you have problems, find bugs, or new ideas you are hereby cordially invited to submit a report of your problem, bug, or idea as explained in the section Bugs, Ideas, Feedback below.

Additionally, you might wish to join the Snit mailing list; see http://www.wjduquette.com/snit for details.

One particular area to watch is using snit::widgetadaptor to adapt megawidgets created by other megawidget packages; correct widget destruction depends on the order of the bindings. The wisest course is simply not to do this.

KNOWN BUGS

HISTORY

During the course of developing Notebook (See http://www.wjduquette.com/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 (see http://www.wjduquette.com/tcl/objects.html), along with a few other tricks I'd picked up since. And though it was working well, it quickly became tiresome because of the amount of boilerplate code associated with each new object type.

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.

If you have any comments or suggestions (or bug reports!) don't hesitate to send me e-mail at [email protected]. In addition, there's a Snit mailing list; you can find out more about it at the Snit home page (see http://www.wjduquette.com/snit).

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, Brian Griffin, Donal Fellows, Miguel Sofer, Kenneth Green, and Anton Kovalenko. If I've forgotten anyone, my apologies; let me know and I'll add your name to the list.

Bugs, Ideas, Feedback

This document, and the package it describes, will undoubtedly contain bugs and other problems. Please report such in the category snit of the Tcllib Trackers. Please also report any ideas for enhancements you may have for either package and/or documentation.

When proposing code changes, please provide unified diffs, i.e the output of diff -u.

Note further that attachments are strongly preferred over inlined patches. Attachments can be made by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar.

KEYWORDS

BWidget, C++, Incr Tcl, Snit, adaptors, class, mega widget, object, object oriented, type, widget, widget adaptors

CATEGORY

Programming tools

COPYRIGHT

Copyright © 2003-2009, by William H. Duquette