snit(n)

πŸ“¦
version 2.0 Β· 2 versions
, in fedora 43, fedora 44, nixos 26.05, nixpkgs unstable

πŸ’– snit(n) πŸ’–

Snit's Not Incr Tcl, OO system


✨ NAME ✨

snit β€” Snit's Not Incr Tcl. That's the whole vibe. πŸ’β™€οΈ

🌈 SYNOPSIS 🌈

package require Tcl 8.5 9

package require snit ?2.3.4?

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 πŸ¦‹

Okay bestie, listen up. Snit is a pure Tcl object and megawidget system. The way it's unique among Tcl object systems? It's based not on inheritance but on delegation. Periodt. πŸ’…

Object systems based on inheritance only let you inherit from classes defined using the same system, which is lowkey limiting. But 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. No cap. ✨

This man page is intended to be a reference only; see the accompanying snitfaq for a gentler, more tutorial introduction to Snit concepts. It's giving "read the FAQ if you're new here". 🫢

🌈 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. Keep an eye out for those, bestie. πŸ‘€

πŸ‘‘ 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 was called (usually the global namespace). It returns the fully qualified name of the new type.

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

The snit::type definition block is a script that may contain the following definitions:

  • typevariable name ?-array? ?value? β€” Defines a type variable with the specified name, and optionally the specified value. Type variables are shared by all instances of the type. If the -array option is included, then value should be a dictionary; it will be assigned to the variable using array set.
  • typemethod name arglist body β€” Defines a type method, a subcommand of the new type command, with the specified name, arglist, and body. 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.

The variable type is automatically defined in the body to the type's fully-qualified name. In addition, type variables are automatically visible in the body of every type method.

If the name consists of two or more tokens, Snit handles it specially:

typemethod {a b} {arg} { puts "Got $arg" }

This statement implicitly defines a type method called a which has a subcommand b. b is called like this:

$type a b "Hello, world!"

a may have any number of subcommands. This makes it possible to define a hierarchical command structure; see method, below, for more examples.

Type methods can call commands from the namespace in which the type is defined without importing them, e.g., if the type name is ::parentns::typename, then the type's type methods can call ::parentns::someproc just as someproc. Snit 1.x Incompatibility: This does not work in Snit 1.x, as it depends on namespace path, a new command in Tcl 8.5.

Snit 1.x Incompatibility: In Snit 1.x, the following two calls to this type method are equivalent:

$type a b "Hello, world!"
$type {a b} "Hello, world!"

In Snit 2.2, the second form is invalid.

  • 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, and contains the type's fully-qualified name. In addition, type variables are automatically visible in the body of the type constructor.

A type may define at most one type constructor.

The type constructor can call commands from the namespace in which the type is defined without importing them, e.g., if the type name is ::parentns::typename, then the type constructor can call ::parentns::someproc just as someproc. Snit 1.x Incompatibility: This does not work in Snit 1.x, as it depends on namespace path, a new command in Tcl 8.5.

  • variable name ?-array? ?value? β€” Defines an instance variable, a private variable associated with each instance of this type, and optionally its initial value. If the -array option is included, then value should be a dictionary; it will be assigned to the variable using array set.
  • method name arglist body β€” Defines an instance method, a subcommand of each instance of this type, with the specified name, arglist and body. The arglist is a normal Tcl argument list and may contain default arguments and the args argument.

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 are automatically visible in all instance methods. If the type has locally defined options, the options array is also visible.

If the name consists of two or more tokens, Snit handles it specially:

method {a b} {} { ... }

This statement implicitly defines a method called a which has a subcommand b. b is called like this:

$self a b "Hello, world!"

a may have any number of subcommands. This makes it possible to define a hierarchical command structure:

% snit::type dog {
    method {tail wag}   {} {return "Wag, wag"}
    method {tail droop} {} {return "Droop, droop"}
}
::dog
% dog spot
::spot
% spot tail wag
Wag, wag
% spot tail droop
Droop, droop
%

So what just happened πŸ‘€ is that we implicitly defined a tail method that has the subcommands wag and droop. Implicitly!! We didn't even write tail anywhere. Which means it is an error to go and define tail explicitly, bestie. Pick a lane. πŸ’…

Methods can call commands from the namespace where the type itself is defined without importing them, like: if the type name is ::parentns::typename, then the type's methods can call ::parentns::someproc just as someproc. ✨ Slay, no import statements needed.

⚠️ Snit 1.x Incompatibility: This does not work in Snit 1.x, because it depends on namespace path, which is a new command in Tcl 8.5. So if you're on old Snit, this is not it. 😭
⚠️ Snit 1.x Incompatibility: In Snit 1.x, the following two calls to this method are equivalent: tcl $self a b "Hello, world!" $self {a b} "Hello, world!" In Snit 2.2, the second form is invalid. So don't do that. 🫢

✨ option ✨

option namespec ?defaultValue?
option namespec ?options...?

Defines an option for instances of this type, and optionally hands it an initial value. If you don't pass a defaultValue, the initial value just defaults to the empty string. (Yes, "". The void. The ✨nothing✨.) πŸŽ€

An option defined this way is said to be locally defined.

The namespec is a list that defines the option's name, resource name, and class name, like:

option {-font font Font} {Courier 12}

The option name must begin with a hyphen, and must not contain any upper case letters. No exceptions, babes. The resource name and class name are optional; if you don't specify them, 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. So this statement is literally equivalent to the previous example:

option -font {Courier 12}

See The Tk Option Database for more info about resource and class names. πŸ“š

Options are normally set and retrieved using the standard instance methods configure and cget; within instance code (method bodies, etc.), option values are available through the options array:

set myfont $options(-font)
🫢 If the type defines any option handlers (e.g., -configuremethod), then it should probably use configure and cget to access its options, to avoid subtle errors. Just saying!! πŸ™ˆ

The option statement may include the following options:

  • -default defvalue β€” Defines the option's default value; otherwise the option's default value will be "". 🌸
  • -readonly flag β€” The flag can be any Boolean value recognized by Tcl. If flag is true, then the option is read-only β€” it can only be set using configure or configurelist at creation time, i.e., in the type's constructor. After that? Locked. πŸ”’
  • -type type β€” Every locally-defined option may define its validation type, which may be either the name of a validation type or a specification for a validation subtype.

For example, an option may declare that its value must be an integer by specifying snit::integer as its validation type:

option -number -type snit::integer

It may also declare that its value is an integer between 1 and 10 by specifying a validation subtype:

option -number -type {snit::integer -min 1 -max 10}

If a validation type or subtype is defined for an option, then it will be used to validate the option's value whenever it is changed by the object's configure or configurelist methods. In addition, all such options will have their values validated automatically immediately after the constructor executes. ✨ Automatic. Iconic. πŸ’…

Snit defines a whole family of validation types and subtypes, and it's quite simple to define new ones. See Validation Types for the complete list, and Defining Validation Types for an explanation of how to define your own. πŸ‘‘

  • -cgetmethod methodName β€” Every locally-defined option may define a -cgetmethod; it is called when the option's value is retrieved using the cget method. Whatever the method's body returns will be the return value of the call to cget. πŸŽ€

The named method must take one argument, the option name. For example, this code is equivalent to (though slower than) Snit's default handling of cget:

option -font -cgetmethod GetOption
method GetOption {option} {
    return $options($option)
}

Note that it's possible for any number of options to share a -cgetmethod. Sharing is caring. 🫢

  • -configuremethod methodName β€” Every locally-defined option may define a -configuremethod; it is called when the option's value is set using the configure or configurelist methods. It is the named method's responsibility to save the option's value; in other words, the value will not be saved to the options() array unless the method saves it there. So if you forget, it's gone. πŸ’€

The named method must take two arguments, the option name and its new value. For example, this code is equivalent to (though slower than) Snit's default handling of configure:

option -font -configuremethod SetOption
method SetOption {option value} {
    set options($option) $value
}

Note that it's possible for any number of options to share a single -configuremethod. ✨

  • -validatemethod methodName β€” Every locally-defined option may define a -validatemethod; it is called when the option's value is set using the configure or configurelist methods, just before the -configuremethod (if any). It is the named method's responsibility to validate the option's new value, and to throw an error if the value is invalid. 🚨

The named method must take two arguments, the option name and its new value. For example, this code verifies that -flag's value is a valid Boolean value:

option -font -validatemethod CheckBoolean
method CheckBoolean {option value} {
    if {![string is boolean -strict $value]} {
        error "option $option must have a boolean value."
    }
}

Note that it's possible for any number of options to share a single -validatemethod. 🌈


✨ 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. Standard Tcl stuff, but make it cute. πŸ’…

As with methods, the arguments type, self, selfns, and win are defined implicitly, and all type and instance variables are automatically visible in its body. πŸ‘‘

If the definition doesn't explicitly define the constructor, Snit defines one implicitly. If the type declares at least one option (whether locally or by delegation), the default constructor will be defined as follows:

constructor {args} {
    $self configurelist $args
}

For standard Tk widget behavior, the argument list should be the single name args, as shown. πŸŽ€

If the definition defines neither a constructor nor any options, the default constructor is defined as follows:

constructor {} {}

As with methods, the constructor can call commands from the namespace in which the type is defined without importing them, e.g., if the type name is ::parentns::typename, then the constructor can call ::parentns::someproc just as someproc. ✨

⚠️ Snit 1.x Incompatibility: This does not work in Snit 1.x, as it depends on namespace path, a new command in Tcl 8.5. Not it, bestie. 😭

✨ destructor body ✨

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

Like, you built it in the constructor, you tear it down in the destructor. Balance. πŸ¦‹

okay so back to the destructor real quick β€” it takes no explicit arguments. Just like with methods, the argument names type, self, selfns, and win are defined implicitly, and literally every type and instance variable is automatically visible in its body. No importing, no setup, it just sees them. πŸ’…

And same energy as methods: the destructor can call commands from the namespace where the type is defined without importing them. So if your type name is ::parentns::typename, then the destructor can call ::parentns::someproc by just writing someproc. so clean ✨

Snit 1.x Incompatibility: this does not work in Snit 1.x, because it depends on namespace path, which is a new command in Tcl 8.5. πŸ’€

🌸 proc name args body 🌸

Defines a new Tcl procedure in the type's namespace.

The proc you get is not a normal Tcl proc, bestie β€” all type variables are automatically visible inside it. πŸ‘‘ It can also reach instance variables, but only if it's passed selfns (with precisely that name, no nicknames allowed) as one of its arguments.

Even though they aren't implicitly defined for procs, you should avoid the argument names type, self, and win. Just don't. πŸ™ˆ

And as with methods and typemethods, procs can call commands from the namespace in which the type is defined without importing them β€” e.g., if the type name is ::parentns::typename, then the proc can call ::parentns::someproc just as someproc. namespace path doing the lord's work πŸ¦‹

Snit 1.x Incompatibility: this does not work in Snit 1.x either, same reason β€” it depends on namespace path, a new command in Tcl 8.5.

πŸŽ€ delegate method name to comp ?as target? πŸŽ€

This one delegates method name to component comp. So when method name gets called on an instance of this type, the method and its arguments get passed to the named component's command instead. Basically you're forwarding the vibe. 🫢

Like, this statement:

delegate method wag to tail

is roughly equivalent to this explicitly defined method:

method wag {args} {
    uplevel $tail wag $args
}

As with methods, the name may have multiple tokens β€” in that case, the last token of the name is assumed to be the name of the component's method. iykyk ✨

The optional as clause lets you specify the delegated method name and maybe add some arguments too:

delegate method wagtail to tail as "wag briskly"
A method cannot be both locally defined and delegated. Pick a lane, bestie. πŸ’€

Note: All forms of delegate method can delegate to both instance components and type components. Equal opportunity delegation. 🌈


πŸ’œ delegate method name ?to comp? using pattern πŸ’œ

In this form of the delegate statement, the using clause is what specifies the precise form of the command that method name is delegated to. And in this form, the to clause is optional, since the chosen command might not involve any particular component at all. Free agent energy. 🧚

The value of the using clause is a list that may contain any or all of the following substitution codes β€” these codes get substituted with the described value to build the delegated command prefix. Note that these two statements are equivalent:

delegate method wag to tail
delegate method wag to tail using "%c %m"

Each element of the list becomes a single element of the delegated command β€” it is never reparsed as a string. Periodt. πŸ’…

Substitutions:

  • %% β€” gets replaced with a single %. So to pass the string %c to the command as an argument, you'd write "%%c".
  • %c β€” gets replaced with the named component's command.
  • %m β€” gets replaced with the final token of the method name; if the method name has one token, this is identical to %M.
  • %M β€” gets replaced by the method name; if the name consists of multiple tokens, they get joined by space characters.
  • %j β€” gets replaced by the method name; if the name consists of multiple tokens, they get joined by underscores ("_").
  • %t β€” gets replaced with the fully qualified type name.
  • %n β€” gets replaced with the name of the instance's private namespace.
  • %s β€” gets replaced with the name of the instance command.
  • %w β€” gets replaced with the original name of the instance command; for Snit widgets and widget adaptors, it'll be the Tk window name. It stays constant, even if the instance command is renamed. loyalty fr fr πŸ¦‹

⭐ delegate method * ?to comp? ?using pattern? ?except exceptions? ⭐

The form delegate method * delegates all unknown method names to the specified component. The except clause lets you list exceptions β€” i.e., method names that will not be delegated this way. The using clause is defined exactly as above. And in this form, the statement must contain the to clause, the using clause, or both. No empty vibes allowed. 🚫

Fun fact: the * can actually be a list of two or more tokens whose last element is *, like this:

delegate method {tail *} to tail

This implicitly defines the method tail whose subcommands will be delegated to the tail component. so clever ✨


πŸ“ delegate option πŸ“

Four flavours, all iconic:

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 β€” and the namespec is defined exactly 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 gets applied to the component, as though the option had been defined with the following -configuremethod and -cgetmethod:

method ConfigureMethod {option value} {
    $comp configure $option $value
}

method CgetMethod {option} {
    return [$comp cget $option]
}

Note that delegated options never appear in the options array. Sneaky little things. πŸ™ˆ

If the as clause is specified, then the target option name is used in place of name.

The form delegate option * delegates all unknown options to the specified component. And the except clause lets you specify a list of exceptions β€” i.e., option names that will not be delegated that way.

Warning: options can only be delegated to a component if it supports the configure and cget instance methods. No configure, no cget, no delegation. That's the rule. πŸ’€
An option cannot be both locally defined and delegated. Again. Pick. A. Lane. 🫠
TBD: Continue from here.

πŸ¦‹ component comp ?-public method? ?-inherit flag? πŸ¦‹

Explicitly declares a component called comp, and automatically defines the component's instance variable for you. So thoughtful. πŸŽ€

If the -public option is specified, then the option is made public by defining a method whose subcommands are delegated to the component β€” e.g., specifying -public mycomp is equivalent to the following:

component mycomp
delegate method {mymethod *} to mycomp

If the -inherit option is specified, then flag must be a Boolean value. If flag is true, then all unknown methods and options get delegated to this component. The name -inherit is basically saying that instances of this new type inherit, in a sense, the methods and options of the component. So -inherit yes is equivalent to:

component mycomp
delegate option * to mycomp
delegate method * to mycomp

inheritance but make it delegation πŸ‘‘


🌈 delegate typemethod name to comp ?as target? 🌈

Delegates type method name to type component comp. So when type method name gets called on this type, the type method and its arguments get passed to the named type component's command instead. Same energy as the instance version, but for the type level. ✨

Like, this statement:

delegate typemethod lostdogs to pound

is roughly equivalent to this explicitly defined method:

typemethod lostdogs {args} {
    uplevel $pound lostdogs $args
}

As with type methods, the name may have multiple tokens β€” and again, the last token of the name is assumed to be the name of the component's method.

The optional as clause lets you specify the delegated method name and maybe add some arguments:

delegate typemethod lostdogs to pound as "get lostdogs"
A type method cannot be both locally defined and delegated. You already know the drill by now, bestie. πŸ’…

πŸ’– delegate typemethod name ?to comp? using pattern πŸ’–

In this form of the delegate statement, the using clause specifies the precise form of the command that type method name is delegated to. In this form, the to clause is optional, since the chosen command might not involve any particular type component at all.

The value of the using clause is a list that may contain any or all of the following substitution codes β€” these codes get substituted with the described value to build the delegated command prefix. Note that these two statements are equivalent:

delegate typemethod lostdogs to pound
delegate typemethod lostdogs to pound using "%c %m"

okay so bestie, every element of that list becomes a single element of the delegated command β€” it is never reparsed as a string. πŸ’… like, no secret re-splitting, it's giving exactly-what-you-wrote energy.

Substitutions: ✨

  • %% β€” gets swapped for a single %. So if you wanna pass the string %c to the command as an argument, you write %%c. escape the escape, periodt πŸ’–
  • %c β€” gets swapped for the named type component's command.
  • %m β€” gets swapped for the final token of the type method name; if the type method name only has one token, this is literally identical to %M.
  • %M β€” gets swapped for the type method name; if the name has multiple tokens, they get joined by space characters.
  • %j β€” gets swapped for the type method name; if the name has multiple tokens, they get joined by underscores ("_").
  • %t β€” gets swapped for the fully qualified type name.

πŸ’« delegate typemethod * ?to comp? ?using pattern? ?except exceptions?

The form delegate typemethod * delegates all unknown type method names to the specified type component. The except clause lets you drop a list of exceptions β€” i.e., type method names that will not be so delegated. The using clause is defined as given above (iykyk πŸ‘€). In this form, the statement must contain the to clause, the using clause, or both. no half-sending it bestie 🫢

Note: By default, Snit interprets $type foo, where foo is not a defined type method, as equivalent to $type create foo, where foo is the name of a new instance of the type. If you use delegate typemethod *, then the create type method must always be used explicitly. 😭 the shortcut is gone, you gotta say it with your chest.

The "*" can be a list of two or more tokens whose last element is "*", as in the following example:

delegate typemethod {tail *} to tail

This implicitly defines the type method tail whose subcommands will be delegated to the tail type component. slay 🌸


πŸŽ€ typecomponent comp ?-public typemethod? ?-inherit flag?

Explicitly declares a type component called comp, and automatically defines the component's type variable. A type component is an arbitrary command to which type methods and instance methods can be delegated; the command's name is stored in a type variable. πŸ’Ύ

If the -public option is specified, then the type component is made public by defining a typemethod whose subcommands are delegated to the type component, e.g., specifying -public mytypemethod is equivalent to the following:

typecomponent mycomp
delegate typemethod {mytypemethod *} to mycomp

If the -inherit option is specified, then flag must be a Boolean value; if flag is true then all unknown type methods will be delegated to this type component. (See the note on "delegate typemethod *", above. πŸ‘†) The name -inherit implies that this type inherits, in a sense, the behavior of the type component. That is, -inherit yes is equivalent to:

typecomponent mycomp
delegate typemethod * to mycomp

🌈 pragma ?options...?

The pragma statement provides control over how Snit generates a type. It takes the following options; in each case, flag must be a Boolean value recognized by Tcl, e.g., 0, 1, yes, no, and so on. πŸ’…

By setting the -hastypeinfo, -hastypedestroy, and -hasinstances pragmas to false and defining appropriate type methods, you can create an ensemble command without any extraneous behavior. clean. minimal. iconic.

  • -canreplace flag β€” If false (the default) Snit will not create an instance of a snit::type that has the same name as an existing command; this prevents subtle errors. 😀 Setting this pragma to true restores the behavior of Snit V0.93 and earlier versions.
  • -hastypeinfo flag β€” If true (the default), the generated type will have a type method called info that is used for type introspection; the info type method is documented below. If false, it will not.
  • -hastypedestroy flag β€” If true (the default), the generated type will have a type method called destroy that is used to destroy the type and all of its instances. The destroy type method is documented below. If false, it will not.
  • -hastypemethods flag β€” If true (the default), the generated type's type command will have subcommands (type methods) as usual. If false, the type command will serve only to create instances of the type; the first argument is the instance name.
    • > This pragma and -hasinstances cannot both be set false. pick a struggle bestie 😭
  • -hasinstances flag β€” If true (the default), the generated type will have a type method called create that is used to create instances of the type, along with a variety of instance-related features. If false, it will not.
    • > This pragma and -hastypemethods cannot both be set false. same vibe, you can't have zero of both 🫠
  • -hasinfo flag β€” If true (the default), instances of the generated type will have an instance method called info that is used for instance introspection; the info method is documented below. If false, it will not.
  • -simpledispatch flag β€” This pragma is intended to make simple, heavily-used abstract data types (e.g., stacks and queues) more efficient. πŸ”₯
    • If false (the default), instance methods are dispatched normally. If true, a faster dispatching scheme is used instead. The speed comes at a price; with -simpledispatch yes you get the following limitations:
      • Methods cannot be delegated. πŸ’€
      • uplevel and upvar do not work as expected: the caller's scope is two levels up rather than one.
      • The option-handling methods (cget, configure, and configurelist) are very slightly slower.

🌸 expose comp

🌸 expose comp as method

Deprecated. To expose component comp publicly, use component's -public option. like, the glow-up already happened, use the new thing bestie πŸ’…


πŸ’œ onconfigure name arglist body

Deprecated. Define option's -configuremethod option instead.

As of version 0.95, the following definitions,

option -myoption
onconfigure -myoption {value} {
    # Code to save the option's value
}

are implemented as follows:

option -myoption -configuremethod _configure-myoption
method _configure-myoption {_option value} {
    # Code to save the option's value
}

πŸ¦‹ oncget name body

Deprecated. Define option's -cgetmethod option instead.

As of version 0.95, the following definitions,

option -myoption
oncget -myoption {
    # Code to return the option's value
}

are implemented as follows:

option -myoption -cgetmethod _cget-myoption
method _cget-myoption {_option} {
    # Code to return the option's value
}

πŸ‘‘ snit::widget name definition

This command defines a Snit megawidget type with the specified name. The definition is defined as for snit::type. A snit::widget differs from a snit::type in these ways: πŸŽ€

  • Every instance of a snit::widget has an automatically-created component called hull, which is normally a Tk frame widget. Other widgets created as part of the megawidget will be created within this widget. 🏠
    • The hull component is initially created with the requested widget name; then Snit does some magic, renaming the hull component and installing its own instance command in its place. The hull component's new name is saved in an instance variable called hull. ✨
  • The name of an instance must be a valid Tk window name, and the parent window must exist. no orphan windows bestie πŸ™ˆ

A snit::widget definition can include any of statements allowed in a snit::type definition, and may also include the following:

πŸ’– widgetclass name

Sets the snit::widget's widget class to name, overriding the default. See The Tk Option Database for more information.

🌟 hulltype type

Determines the kind of widget used as the snit::widget's hull. The type may be frame (the default), toplevel, labelframe; the qualified equivalents of these, tk::frame, tk::toplevel, and tk::labelframe; or, if available, the equivalent Tile widgets: ttk::frame, ttk::toplevel, and ttk::labelframe. In practice, any widget that supports the -class option can be used as a hull widget by lappend'ing its name to the variable snit::hulltypes. 🌈

snit::widgetadaptor name definition β€” this one builds you a Snit megawidget type with the name you give it, bestie. The tea? It's not like snit::widget, because the instance's hull component does not get created automatically for you. Nope! You make it yourself in the constructor, then install it with the installhull command. Once the hull is installed, its instance command gets renamed and replaced, exactly like it does with regular snit::widgets. And your original command? Still there, still yours, living happily in the instance variable hull. πŸ’…

Heads up: generally speaking, you cannot change the widget class of a snit::widgetadaptor's hull widget. That's just how it is, no cap.

Peep The Tk Option Database for the deets on how snit::widgetadaptors get along with the option database. πŸŽ€


snit::typemethod type name arglist body β€” defines a brand-new type method (or redefines one that already exists) for a type that already exists. Glow-up for your old type, periodt. ✨

snit::method type name arglist body β€” defines a fresh instance method (or redefines an existing one) for a previously existing type. Lowkey important caveat though: delegated instance methods cannot be redefined. Sorry bestie, that door is locked. πŸ”’

snit::macro name arglist body β€” makes a Snit macro with the name, arglist, and body you specify. Macros are the whole vibe for defining new type and widget definition statements in terms of the statements in this very man page. πŸ¦‹

A macro is literally just a Tcl proc defined in the slave interpreter that compiles type and widget definitions. So yeah, macros get access to all the type and widget definition statements. Want the full lore? Go read Macros and Meta-programming. πŸ‘‘

Your macro name can't be the same as any standard Tcl command, or any Snit type or widget definition statement. Like, you can't redefine the method or delegate statements, or the standard set, list, or string commands. That's a hard no. πŸ’€

snit::compile which type body β€” so here's how Snit defines a type, widget, or widgetadaptor: it "compiles" the definition into a Tcl script, and then that script gets evaluated in the Tcl interpreter, which is what actually defines the new type. This command is the one that exposes that "compiler." 🌈

Give it a definition body for the named type, where which is type, widget, or widgetadaptor, and snit::compile hands you back a list of two elements:

  • element one = the fully qualified type name
  • element two = the definition script

Why would you even want this? snit::compile is iconic when extra processing needs to happen on the Snit-generated code β€” like if it has to be instrumented, for example, or run through the TclDevKit compiler. Also, that returned script can be saved in a ".tcl" file and used to define the type as part of an application or library, which saves you the compilation overhead at app start-up. Chef's kiss. 🫢

Note that the same version of Snit must be used at run-time as at compile-time. Don't mix and match, it's not giving. 😭

πŸŽ€ THE TYPE COMMAND πŸŽ€

A type or widget definition creates a type command, and that command is what you use to create instances of the type. Here's its form:

$type typemethod args...

The typemethod can be any of the Standard Type Methods (like create, for example), or any type method defined in the type definition. The args after it depend on whichever typemethod you picked. 🌸

So here's the tea: the type command is most often used to create new instances of the type. Which means the create method is assumed if the first argument to the type command doesn't name a valid type method β€” unless the type definition includes delegate typemethod * or the -hasinstances pragma is set to false. πŸ’…

And it gets better. If the -hastypemethods pragma is false, then Snit type commands can be called with no arguments at all, and in that case the type command creates an instance with an automatically generated name. In other words, as long as the -hastypemethods pragma is false and the type has instances, these three commands are all equivalent:

snit::type dog { ... }

set mydog [dog create %AUTO%]
set mydog [dog %AUTO%]
set mydog [dog]

Cute, right? 🐢 But this does not work for Snit widgets, for obvious reasons. πŸ™ˆ

Snit 1.x Incompatibility: In Snit 1.x, that behavior above is available whether -hastypemethods is true (the default) or false.

✨ STANDARD TYPE METHODS ✨

On top of whatever type methods live in the type's definition, all type and widget commands will usually have at least these subcommands:

  • $type create name ?option value ...? β€” creates a new instance of the type, giving it the name you specified and calling the type's constructor.

For snit::types, if name isn't a fully-qualified command name, it's assumed to be a name in the namespace where the call to snit::type shows up. The method returns the fully-qualified instance name. πŸ’–

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

As long as name doesn't conflict with any defined type method name, the create keyword may be omitted β€” unless the type definition includes delegate typemethod * or the -hasinstances pragma is set to false.

If the name includes the string %AUTO%, it gets 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 after the name are a list of option names and their values β€” but a type's constructor can specify a different argument list.

As of Snit V0.95, create will throw an error if the name is the same as any existing command (note that this was always true for snit::widgets and snit::widgetadaptors). You can restore the previous behavior using the -canreplace pragma. πŸ‘‘

  • $type info typevars ?pattern? β€” returns a list of the type's type variables (Snit internal variables excluded); all the variable names are fully-qualified.

If pattern is given, it's used as a string match pattern; only names that match the pattern come back. πŸŽ€

  • $type info typemethods ?pattern? β€” returns a list of the names of the type's type methods. If the type has hierarchical type methods, whether locally-defined or delegated, only the first word of each one gets included in the list.

If the type definition includes delegate typemethod *, the list will include only the names of those implicitly delegated type methods that have been called at least once and are still in the type method cache.

If pattern is given, it's used as a string match pattern; only names that match the pattern come back. ✨

  • $type info args method β€” returns a list holding the names of the arguments to the type's method, in order. This method cannot be applied to delegated type methods. πŸ’€
  • $type info body method β€” returns the body of typemethod method. This method cannot be applied to delegated type methods either.
  • $type info default method aname varname β€” returns a boolean value saying whether the argument aname of the type's method has a default value (true) or not (false). If the argument does have a default, its value gets placed into the variable varname. πŸ¦‹
  • $type info instances ?pattern? β€” returns a list of the type's instances. For snit::types, it's a list of fully-qualified instance names; for snit::widgets, it's a list of Tk widget names.

If pattern is given, it's used as a string match pattern; only names that match the pattern come back.

Snit 1.x Incompatibility: In Snit 1.x, the full multi-word names of hierarchical type methods are included in the return value.
  • $type destroy β€” destroys the type's instances, the type's namespace, and the type command itself. Gone. Poof. πŸ’…

🌈 THE INSTANCE COMMAND 🌈

A Snit type or widget's create type method creates objects of the type, and every object has a unique name that is also a Tcl command. That command is how you access the object's methods and data, and it looks like this:

$object method args...

The method can be any of the Standard Instance Methods, or any instance method defined in the type definition. The args after it depend on the specific method you chose. 🌸


πŸ’œ STANDARD INSTANCE METHODS πŸ’œ

On top of any delegated or locally-defined instance methods in the type's definition, all Snit objects will have at least these subcommands:

  • $object configure ?option? ?value? ... β€” assigns new values to one or more options. Called with one argument (an option name), it returns a list describing the option, just like Tk widgets do; called with no arguments, it returns a list of lists describing all the options, again just like Tk widgets do. 🫢
Warning: This information will be available for delegated options only if the component they're delegated to has a configure method that returns this same kind of information.
Note: Snit defines this method only if the type has at least one option.
  • $object configurelist optionlist β€” like configure, but it takes one argument, a list of options and their values. Mostly useful in the type constructor, but honestly you can use it anywhere. πŸ’–
Note: Snit defines this method only if the type has at least one option.
  • $object cget option β€” returns the option's value.
Note: Snit defines this method only if the type has at least one option.
  • $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 get destroyed by calling Tk's destroy command, just like normal widgets are.
  • $object info type β€” returns the instance's type. πŸŽ€
  • $object info vars ?pattern? β€” returns a list of the object's instance variables (Snit internal variables excluded). The names are fully qualified.

If pattern is given, it's used as a string match pattern; only names that match the pattern come back. πŸ¦‹

  • $object info typevars ?pattern? β€” returns a list of the object's type's type variables (Snit internal variables excluded). The names are fully qualified.

If pattern is given, it's used as a string match pattern; only names that match the pattern come back. πŸ‘‘

πŸ¦‹ $object info typemethods ?pattern?

bestie this one hands you back a list of the names of the type's type methods. ✨ and here's the tea: if the type has hierarchical type methods β€” whether they're locally-defined or delegated β€” only the first word of each one gets included in the list. that's it, just the first word, no more. πŸ’…

if the type definition includes delegate typemethod *, then the list will include only the names of those implicitly delegated type methods that have been called at least once and are still hanging out in the type method cache. they have to have been called, fr fr. πŸ‘‘

if pattern is given, it's used as a string match pattern; only names that match the pattern come back to you.

πŸ’€ Snit 1.x Incompatibility: in Snit 1.x, the full multi-word names of hierarchical type methods are included in the return value. we do not do that anymore, periodt.

🌸 $object info options ?pattern?

gives you a list of the object's option names. this always includes local options and explicitly delegated options. and if unknown options are delegated too, and if the component they're delegated to responds to $object configure like Tk widgets do, then the result will include all possible unknown options that can be delegated to the component. so like, the whole possibility space of options. iconic.

if pattern is given, it's used as a string match pattern; only the names that match get returned.

πŸ™ˆ heads up: the return value might be different for different instances of the same type, if component object types can vary from one instance to another. no cap.

πŸŽ€ $object info methods ?pattern?

returns a list of the names of the instance's methods. if the type has hierarchical methods β€” locally-defined or delegated, either way β€” only the first word of each is included in the list.

if the type definition includes delegate method *, the list will include only the names of those implicitly delegated methods that have been called at least once and are still in the method cache. πŸ”₯

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

πŸ’€ Snit 1.x Incompatibility: in Snit 1.x, the full multi-word names of hierarchical type methods are included in the return value. different era, bestie.

🧚 $object info args method

returns a list containing the names of the arguments to the instance's method, in order. this method cannot be applied to delegated methods β€” sorry, that's just not a thing. 😭

πŸ’œ $object info body method

returns the body of the instance's method method. this method cannot be applied to delegated methods either. nope nope.

⭐ $object info default method aname varname

returns a boolean value telling you whether the argument aname of the instance's method has a default value (that's true) or not (false). and if the argument does have a default, its value gets placed into the variable varname. 🫢


πŸ’– COMMANDS FOR USE IN OBJECT CODE πŸ’–

Snit defines the following commands for use in your object code β€” meaning, for use in type methods, instance methods, constructors, destructors, onconfigure handlers, oncget handlers, and procs. they do not live in the ::snit:: namespace; instead, they are created with the type, and you can use them without qualification. no prefix needed, we're chill like that.

🌈 mymethod name ?args...?

the mymethod command is for formatting callback commands to pass 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 these will cause the object's dosomething method to be called when the $button is pressed:

$button configure -command [list $self dosomething myargument]

$button configure -command [mymethod dosomething myargument]

the chief distinction between the two? the latter form will not break if the object's command is renamed. main character energy, honestly. πŸ‘‘

πŸ¦‹ mytypemethod name ?args...?

mytypemethod is also for formatting callback commands to pass to other objects. it returns a command that, when called, will invoke type method name with the specified arguments, plus of course any arguments added by the caller. so both of these will cause the object's dosomething type method to be called when $button is pressed:

$button configure -command [list $type dosomething myargument]

$button configure -command [mytypemethod dosomething myargument]

type commands cannot be renamed, so in practice there's little difference between the two forms. mytypemethod is provided for parallelism with mymethod. it's giving symmetry. ✨

πŸŽ€ myproc name ?args...?

myproc β€” same deal, formatting callback commands for other objects. it returns a command that, when called, will invoke the type proc name with the specified arguments, plus of course any arguments added by the caller. so both of these will cause the object's dosomething proc to be called when $button is pressed:

$button configure -command [list ${type}::dosomething myargument]

$button configure -command [myproc dosomething myargument]

🌸 myvar 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. 🫢

πŸ’œ mytypevar name

given a 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.

🧚 from argvName option ?defvalue?

the from command plucks an option value from a list of options and their values, like the one 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's 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. and if the option is a locally-defined option, and defvalue is not specified, then the option's default value as specified in the type definition will be returned instead. πŸ’…

πŸ‘‘ install compName using objType objName args...

creates a new object of type objType called objName and installs it as component compName, as described in Components and Delegation. any additional args... are passed along with the name to the objType command. if this is a snit::type, then the following two commands are equivalent:

install myComp using myObjType $self.myComp args...

set myComp [myObjType $self.myComp args...]
⚠️ note that whichever method you use, compName must still be declared in the type definition using component, or must be referenced in at least one delegate statement. no sneaking around it.

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 Tk Option Database for more info.

install cannot be used to install type components; just assign the type component's command name to the type component's variable instead. 😀

πŸ“ 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. note that the installed widget's name must be $win. 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. 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. 🌈

see The Tk Option Database for more information about snit::widgetadaptors and the option database.

🌟 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 code (e.g., method bodies). however, instance code can use the variable command to declare instance variables that don't appear in the type definition, and also to bring variables from other namespaces into scope in the usual way.

it's generally clearest to define all instance variables in the type definition, and omit declaring them in methods and so forth. do the tidy thing bestie. πŸŽ€

note that this is an instance-specific version of 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 that don't appear in the type definition.

it's generally clearest to declare all type variables in the type definition, and omit declaring them in methods, type methods, etc. ✨

πŸ’€ varname name

Deprecated. use myvar instead. i'm literally begging you. πŸ™ˆ

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

Deprecated. use mytypevar instead. this one's giving legacy, respectfully. 😭

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

πŸ’« codename name

Deprecated, bestie. Use myproc instead, fr fr.

Okay so you hand this one the name of a proc β€” and heads up, it has to be an actual proc, not a type method or an instance method, those do not count β€” and it hands you back the fully-qualified command name, all nice and ready to be passed as a callback. πŸ’…


🐢 COMPONENTS AND DELEGATION

So like, when an object includes other objects β€” think a toolbar that contains buttons, or a GUI object that contains an object referencing a database β€” that included object is what we call 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 this 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 super easy to reach from any method. Periodt.

The install command gives you an alternate way to create and install that component:

snit::type dog {
    component mytail

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

    method wag {} {
        $mytail wag
    }
}

For snit::types, those two methods are equivalent; but for snit::widgets and snit::widgetadaptors, the install command properly initializes the widget's options by querying The Tk Option Database. πŸ‘‘

In the examples above, the dog object's wag method just calls the tail component's wag method. In OO jargon, that's called delegation. And Snit has an easier way to do it:

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 β€” do it, it's cute); it also defines the dog object's wag method, delegating it to the mytail component. 🫢

If you want, all otherwise unknown methods can be delegated to one 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 β€” one that neither dog nor tail recognizes β€” will simply raise an error. πŸ’€

Option delegation is similar to method delegation, except for the interactions with the Tk option database; that whole situation is described in The Tk Option Database. πŸŽ€


🌈 TYPE COMPONENTS AND DELEGATION

The relationship between type components and instance components is identical to the one between type variables and instance variables, and to the one between type methods and instance methods. Just as an instance component is an instance variable that holds the name of a command, 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. ✨

And just as delegate method can be used to delegate methods to instance components (as described in Components and Delegation), delegate typemethod can be used to delegate type methods to type components. 🧚

Also note: as of Snit 0.95, delegate method can delegate methods to both instance components and type components. We love range.


πŸ‘‘ THE TK OPTION DATABASE

This section describes how Snit interacts with the Tk option database, and it assumes you already have 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, and so 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 a great deal about what Snit requires. Here's the brief version of the requirements, for reference:

  • If the snit::widget's default widget class is not what is desired, set it explicitly using widgetclass in the widget definition.
  • When defining or delegating options, specify the resource and class names explicitly when the defaults aren't what you want.
  • Use installhull using to install the hull for snit::widgetadaptors.
  • Use install to install all other components.

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 it repays attention to detail. πŸ™ˆ

Setting the widget class

Every Tk widget has a widget class. For Tk widgets, the widget class name is 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 it 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... and sometimes they absolutely cannot. 😭

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 β€” and this is the plot twist β€” the caller has explicitly overridden the default, so the new widget will be "greasy". periodt πŸ’…

Initializing options delegated to the hull ✨

A snit::widget's hull is a widget, bestie. And once its class has been set, it is expected to query the option database for itself. The one exception is options that get delegated to it under a different name. Consider this 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? 🧐 Let's unpack it like a group chat screenshot.

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. She's just vibing. πŸ¦‹

The value of B is "red". The hull will automatically pick up the value "green" for its -background option, just like it picked up the -relief value. BUT β€” 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. main character energy tbh ✨

The value of C is also "red", because -background is implicitly delegated to the hull; so 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. iykyk

The value of D is "5", but not for the reason you think. 😭 Note that as it's 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 the correct thing has happened, and doesn't worry about it any further. she said "not my problem" πŸ’…

For snit::widgetadaptors, 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 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.

  1. First, a component widget remains a widget still, and therefore is initialized from the option database in the usual way.
  2. 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:

  • Builds a list of the options explicitly included in the install command β€” in this case, -foreground.
  • Queries the option database for all options delegated explicitly to the named component.
  • Creates the component using the specified command, after inserting into it a list of options and values read from the option database. Thus, the explicitly included options (-foreground) will override anything read from the option database.
  • If the widget definition implicitly delegated options to the component using delegate option *, then Snit calls the newly created component's configure method to receive a list of all of the component's options. From this Snit builds a list of options implicitly delegated to the component that were not explicitly included in the install command. For all such options, Snit queries the option database and configures the component accordingly.

Non-widget components 🧚

The option database is never queried for snit::types, since it can only be queried given a Tk widget name. rip πŸ’€ However, snit::widgets 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. we love a little meta moment ✨

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
}
heads up bestie: 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::macros, 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. vibe check but for types πŸ’…

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. no cap

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:

πŸ’– snit::boolean validate ?value?

πŸ’– snit::boolean name

Validates Tcl boolean values: 1, 0, on, off, yes, no, true, false. It's possible to define subtypes β€” that is, instances β€” of snit::boolean, but as it has no options there's no reason to do so. like why would you πŸ™ˆ

πŸ’– snit::double validate ?value?

πŸ’– snit::double name ?option value...?

Validates floating-point values. Subtypes may be created with the following options:

  • -min min β€” Specifies a floating-point minimum bound; a value is invalid if it is strictly less than min.
  • -max max β€” the floating-point maximum bound, bestie. If your value is strictly greater than max, it's invalid. Periodt. πŸ’…

✨ snit::enum ✨

snit::enum validate ?value?

snit::enum name ?option value...?

Okay so this one validates that a value comes from an enumerated list β€” like, it has to be one of these options, no improvising. πŸŽ€

Lowkey important: the base type is basically useless on its own, because only the subtypes actually have an enumerated list to validate against. So don't be out here using snit::enum raw like that. πŸ™ˆ

Subtypes get made with these options:

  • -values list β€” gives you the list of valid values. A value is valid if and only if it's included in that list. That's the whole vibe check. ✨

🌸 snit::fpixels 🌸

snit::fpixels validate ?value?

snit::fpixels name ?option value...?
Tk programs only! 🚨

This one validates screen distances, in any of the forms accepted by winfo fpixels. Subtypes can be created with these options:

  • -min min β€” the minimum bound. A value is invalid if it's strictly less than min. And the bound itself can be expressed in any of the forms accepted by winfo fpixels. πŸ’–
  • -max max β€” the maximum bound. A value is invalid if it's strictly greater than max. Same deal, the bound can be in any of the forms accepted by winfo fpixels. πŸ’–

πŸ¦‹ snit::integer πŸ¦‹

snit::integer validate ?value?

snit::integer name ?option value...?

Validates integer values. Math girlies rise up. πŸ”₯ Subtypes get made with:

  • -min min β€” integer minimum bound. A value is invalid if it's strictly less than min.
  • -max max β€” integer maximum bound. A value is invalid if it's strictly greater than max.

πŸ’œ snit::listtype πŸ’œ

snit::listtype validate ?value?

snit::listtype name ?option value...?

Validates Tcl lists. Subtypes can be created with these options:

  • -minlen min β€” minimum list length. The value is invalid if it has fewer than min elements. Defaults to 0. πŸ’•
  • -maxlen max β€” maximum list length. The value is invalid if it has more than max elements.
  • -type type β€” specifies the type of the list elements. And type has to be the name of a validation type or subtype, okay? So in this next example, the value of -numbers has to be a list of integers:
option -numbers -type {snit::listtype -type snit::integer}
⚠️ Heads up bestie: this option does not support defining new validation subtypes on the fly. Like, this code right here will not work (yet, anyway):
option -numbers -type {
    snit::listtype -type {snit::integer -min 5}
}

Instead, you define the subtype explicitly, like the icon you are:

snit::integer gt4 -min 5

snit::type mytype {
    option -numbers -type {snit::listtype -type gt4}
}

See how clean that is? That's the move. ✨


🧚 snit::pixels 🧚

snit::pixels validate ?value?

snit::pixels name ?option value...?
Tk programs only! 🚨

Validates screen distances, in any of the forms accepted by winfo pixels. Subtypes may be created with the following options:

  • -min min β€” the minimum bound. A value is invalid if it's strictly less than min. The bound may be expressed in any of the forms accepted by winfo pixels. 🌈
  • -max max β€” the maximum bound. A value is invalid if it's strictly greater than max. Again, bound can be in any of the forms accepted by winfo pixels. 🌈

πŸŽ€ snit::stringtype πŸŽ€

snit::stringtype validate ?value?

snit::stringtype name ?option value...?

Validates Tcl strings. Now, the base type is of little use by itself, since every Tcl value is also a valid string β€” so it's giving "everything passes, why am I even here" energy. 😭 Subtypes may be created with these options:

  • -minlen min β€” minimum string length. The value is invalid if it has fewer than min characters. Defaults to 0. πŸ’•
  • -maxlen max β€” maximum string length. The value is invalid if it has more than max characters.
  • -glob pattern β€” specifies a string match pattern. The value is invalid if it doesn't match the pattern.
  • -regexp regexp β€” specifies a regular expression. The value is invalid if it doesn't match the regular expression.
  • -nocase flag β€” by default, both -glob and -regexp matches are case-sensitive. But if -nocase is set to true, then both -glob and -regexp matches become case-insensitive. Slay. πŸ’…

πŸ‘‘ snit::window πŸ‘‘

snit::window validate ?value?

snit::window name
Tk programs only! 🚨

Validates Tk window names. The value has to make winfo exists return true β€” otherwise, invalid, sorry babe. πŸ™ˆ

It's possible to define subtypes β€” that is, instances β€” of snit::window, but since it has no options at present, there's literally no reason to do so. Not me doing all that work for nothing. πŸ’€


✨ DEFINING VALIDATION TYPES ✨

Okay so there are three ways to define a new validation type:

  1. as a subtype of one of Snit's validation types,
  2. as a validation type command,
  3. 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. (You already know, iykyk. 🫢)


🌟 the validation type command era 🌟

The next simplest way to create a new validation type is as a validation type command.

So here's the tea: a validation type is simply an object that has a validate method. And that validate method must:

  • take one argument, a value,
  • return the value if it's valid,
  • and throw an error with -errorcode INVALID if the value is invalid.

That's it. That's the whole assignment. And you can do it 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 this way cannot be subtyped, of course β€” but honestly? For many applications this will be sufficient. Don't overthink it. πŸ’…


🌈 the full-fledged, subtype-able era 🌈

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. Main character energy, honestly. πŸ‘‘

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. Go look, it's giving study material. πŸ“šβœ¨


πŸ’– 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. We want to hear it, fr fr. 🫢

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

One particular area to watch out for is using snit::widgetadaptor to adapt megawidgets created by other megawidget packages. Correct widget destruction depends on the order of the <Destroy> bindings. 😭

⚠️ The wisest course is simply not to do this. Like, just don't. πŸ’€

πŸ’€ KNOWN BUGS πŸ’€

  • Error stack traces returned by Snit 1.x are extremely ugly and typically contain far too much information about Snit internals. The error messages are much improved in Snit 2.2. Glow up confirmed. ✨
  • Also see the Project Trackers as explained in the section Bugs, Ideas, Feedback below. πŸ‘€

πŸ“œ HISTORY (storytime, bestie) πŸ“œ

Okay so picture this: while building Notebook (peep http://www.wjduquette.com/notebook β€” his Tcl-based personal notebook app, iconic), our guy realized he was writing it as a whole collection of objects. And no, he wasn't using any particular object-oriented framework β€” he was just out here writing objects in pure Tcl, following the guidelines from his own Guide to Object Commands (see http://www.wjduquette.com/tcl/objects.html), plus a few extra tricks he'd picked up along the way since then. And like... it was working great, fr fr. But it got tiresome SO fast because of how much boilerplate code came with every single new object type. πŸ’€

So that was one thing β€” tedium is a powerful motivator, no cap. But the other thing he noticed? He wasn't using inheritance at all. Not even a little. And he wasn't missing it either. Instead he was using delegation: objects that created other objects and delegated methods to them. πŸ¦‹

And he said to himself, "This is getting tedious... there has got to be a better way." And then one afternoon, on a whim, he 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. Periodt. ✨

If you have any comments or suggestions (or bug reports!! πŸ‘€) don't hesitate to send him e-mail at will@wjduquette.com. Also 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. That's the origin story, bestie. πŸ‘‘

That said, much credit belongs to the following people for using Snit and providing 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
  • Anton Kovalenko

If he's forgotten anyone, my apologies β€” let him know and he'll add your name to the list. 🫢


πŸ› BUGS, IDEAS, FEEDBACK πŸ›

This document, and the package it describes, will undoubtedly contain bugs and other problems. Like, that's just how it is, no shade. πŸ™ˆ

Please report such in the category snit of the Tcllib Trackers at http://core.tcl.tk/tcllib/reportlist. Please also report any ideas for enhancements you may have for either the package and/or the documentation. He wants to hear it, fr fr.

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

Also 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. That's the move. πŸ’…


πŸ”‘ KEYWORDS πŸ”‘

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


πŸŽ€ CATEGORY πŸŽ€

Programming tools


Copyright (c) 2003-2009, by William H. Duquette


And that's a wrap on the whole Snit page, bestie!! 🌸 This little gem lives in tcllib, sitting pretty at version 2.3.4, and this edition is dated 2.3.4 β€” go forth and build your objects, you absolute legend. Slay. πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from