This binding operates by starting a "wish" interpreter as a slave process and passing both Tcl routines and data between the Mindy interpreter and the slave process. However, Dylan/TK provides an abstraction layer that should vastly reduce or even eliminate the need for users to be familiar with the Tcl extension language. For example, the following Dylan code:
define constant text-frame = make(<frame>, height: 500, fill: "both", side: "bottom", expand: #t); define constant text-window = make(<text>, in: text-frame, relief: "sunken", font: normal-font, fill: "both", side: "right", expand: #t); define constant text-scroll = scroll(text-window, in: text-frame, fill: "y");could be used to produce the effect as the following Tcl/Tk code:
frame .text-frame -height 500 pack .text-frame -fill both -side bottom -expand 1 text .text-frame.text -relief sunken -font normal-font pack .text-frame.text -in .text-frame -fill both -side right -expand 1 scrollbar .text-frame.scroll -command {.text-frame.text yview } .text-frame.text configure -yscrollcommand {.text-frame.scroll set } pack .text-frame.scroll -in .text-frame -fill ywhile being considerably clearer to the average Dylan programmer.
Note: In order for this implementation to work you must have a copy of "wish" available on your system. This implementation was developed upon version 3.6 of "Tcl/Tk" but should work any version Tk implementation after 3.3 and before 4.0. Since version 4 introduces incompatible changes, you should not
(at present) expect all features to work under 4.0 or later
Any Dylan program which uses the Tk library will automatically create a top level window (which is accessible via the *root-window* variable) and a separate thread to process events upon that window. You build an interface by making widgets and "packing" them into windows -- either the root window, newly created top-level windows, or into subwindows of these windows.
An simple application might be the following:
define method main (program-name :: <string>, #rest args); make(<message>, text: "Hello, world!", aspect: 500, side: "top"); make(<button>, text: "Okay", relief: "raised", command: curry(destroy-window, *root-window*), expand: #t); map-window(*root-window*); end method main;This creates a text message and a pushbutton, packs them into the root window, and then exits, and makes the root window visible. It then exits the main thread, leaving just the event processing thread active. When the button is pressed, it will destroy the root window (and all its subwindows). Since there are no windows left, the event thread exits, and the program terminates. We could just as easily specified "command: exit", which would force the Dylan program to exit, killing off all threads, and thus destroying the windows. Go ahead and try it, if you like.
Widgets, like any other Dylan object, are typically created via "make". There are three different varieties of keywords which can be specified:
and will occasionally include others on a case-by-case basis. In addition, the following new classes are also frequently accepted as parameter values:
Those Dylan/Tk functions which return values typically cast the results back into meaningful Dylan types before returning them. However, there are some occasions where this proves impractical, and result values remain as <string>s which must be explicitly cast into appropriate types. For example, the "configuration" function returns the values of all the configuration options for a widget. Since it can't guess what the types of these values ought to be, they simply remain as <string>s.
In some cases you must specify "callback" functions for Dylan/Tk widgets. These are typically specified via the "command:" keyword to make. On the few occasions where these functions take parameters, they will be passed in as <string>, and may include "\" quotation characters. You may need to call "tk-unquote" to strip out backslashes or to call "tk-as" to convert this to another type. For example:
tk-as(<boolean>, "1")returns #t.
Other options are by no means universal, but tend to mean the same thing whenever they occur:
You also have the right to specify a "debug name" for the window via the "name:" option. If you refuse this right, one will be created for you automatically.
One window is automatically created as a result of importing the "tk" library. This is the top level window "*root-window*".
There are a small set of methods which operate upon all windows:
Each "slave" (i.e. non-top-level) window must be packed into some other window. You specify this window either directly, via "in: parent" or indirectly via "before: sibling" or "after: sibling". (The latter options specify that the window should have the same parent as the specified "sibling", and that they should be placed just before or after the sibling in the parent's "packing order".) If no parent is specified explicitly, Dylan/Tk implicitly assumes "in: *root-window*".
The "side:" option specifies where the widget should be placed relative to the ones which follow it in the "packing order". If you specify "side: "left"", then all widgets which are packed into the parent after this one (either because they were created later or because of a "before:" or "after:" option) will be placed somewhere to the right of this widget. Note that it will not necessarily be placed on the parent's absolute left side, because all windows which precede this one in the packing order have "first dibs" on prime real estate. Possible values for this option are "left", "right", "top", and "bottom" -- the default is "left".
If you were to execute:
let w1 = make(<listbox>, in: *root-window*, side: "left"); let w2 = make(<text>, before: w1, side: "top"); let w3 = make(<scrollbar>, after: w1);you would end up with something like:
+++++++++++++++++ | w2 | +++++++++++++++++ | w1 |w| | |3| +++++++++++++++++Window W2 comes first in the packing order (because of the "before:" option) and preempts the top of the root window. W1 comes second, and grabs the left side of the remaining space, leaving a small chunk on the right for W3 (and any windows which might be packed after W3).
The other options available are "anchor:", "expand:", "fill:", "padx:", and "pady:", which are explained in the book. "Expand:" typically takes a boolean value, while "fill:" takes one of "x", "y", "both", or "none".
The "unpack" function will remove a window from the control of the "packer" and, as a side effect, remove it from the display. The window will still exist and can be updated or repacked via the "pack" function. You will, however, get errors if you try to specify a different parent when you repack it.
function bind (window, event :: <string>, command) => (window);This function specifies an action to be performed when an X event matching "event" occurs. Typically this is a nil-adic Dylan function, but it may also be an arbitrary line of Tcl code. A typical binding might be the following:
bind(l1, "<Double-Button-1>", method () do(print-config, current-selection(l1)) end method);This would cause Dylan/Tk to call "print-config" on each of the current selections in the <listbox> "l1" whenever the leftmost mouse button is double-clicked.
There are two other utility functions which allow you to determine the bindings for a particular window:
"Make" on "<active-variable>" requires a "value:" keyword and accepts an optional "class:" keyword. If specified, the class defines the logical type of the variable's value. The "value" slot will always contain a value of that type, although "value-setter" will accept arbitrary types (especially <string>) and attempt to convert them to the given type.
You may also specify a Dylan function to be executed when an active variable's value changes (i.e. becomes \~= to the old value). This method is specified via the "command:" keyword to "make". It will be invoked with both the new and the old value at some point after the value is changed. Note that the value is updated immediately (i.e. asynchronously), while the calls to the given command are executed sequentially along with event callbacks.
Active variables will typically be passed as values of "variable:" or "text-variable:" keywords.
function activate (button) => button; function deactivate (button) => button; function invoke (button) => button;
function activate (button) => button; function deactivate (button) => button; function select-value (button) => button; function deselect-value (button) => button; function toggle-value (button) => button;
function activate (button) => button; function deactivate (button) => button;
function activate (button) => button; function deactivate (button) => button; function select-value (button) => button; function deselect-value (button) => button;
function xview (canvas :: <canvas>, index) => canvas; function yview (canvas :: <canvas>, index) => canvas; function focus (canvas :: <canvas>) => result :: <canvas-item>; function focus-setter (value :: <canvas-item>, canvas :: <canvas>) => (); function scan-mark (window :: <canvas>, #rest coords) => window; function scan-dragto (window :: <canvas>, #rest coords) => window; function select-item (window :: <canvas>, index) => window; function create-arc (canvas, x1, y1, x2, y2, #rest opts) => item; Options include: #"extent", #"fill", #"outline", #"start", #"stipple", #"style", #"width". function create-bitmap (canvas, x1, y1, #rest opts) => item; Options include: #"anchor", #"bitmap". function create-line (canvas, points :: <sequence>, #rest opts) => item; Options include: #"arrow", #"arrowshape", #"capstyle", #"fill", #"joinstyle", #"smooth", #"splinesteps", #"stipple", #"width". function create-oval (canvas, x1, y1, x2, y2, #rest opts) => item; Options include: #"fill", #"outline", #"stipple", #"width". function create-polygon (canvas, pnts :: <sequence>, #rest opts) => item; Options include: #"fill", #"smooth", #"splinesteps", #"stipple". function create-rectangle (canvas, x1, y1, x2, y2, #rest opts) => item; Options include: #"fill", #"outline", #"stipple", #"width". function create-text (canvas, x1, y, #rest opts) => item; Options include: #"anchor", #"fill", #"font", #"justify", #"stipple", #"text", #"width". function create-window (canvas, x1, y1, #rest opts) => item; Options include: #"anchor", #"height", #"width", #"window". function postscript (canvas, #rest opts) => result :: <string>; Options include: #"colormap", #"colormode", #"fontmap", #"height", #"pageanchor", #"pageheight", #"pagewidth", #"pagex", #"pagey", #"rotate", #"width", #"x", #"y".
function icursor (entry, index) => entry; function view (entry, index) => entry; function delete (entry, index, #key end: last) => entry; function insert (entry, index, #rest elements) => entry; function get-all (entry) => result :: <string>; function get-elements (entry, index, #key end) => result :: <string>; function scan-mark (entry, #rest coords) => entry; function scan-dragto (entry, #rest coords) => entry; function select-adjust (entry, index) => entry; function select-clear (entry) => entry; function select-from (entry, index) => entry; function select-to (entry, index) => entry;
Supports no operations.
Supports no operations.
function current-selection(listbox, #rest rest) => indices :: <sequence>; function nearest(listbox, y-coord) => index :: <integer>; function size(listbox) => result :: <integer>; function xview(listbox, index) => listbox; function yview(listbox, index) => listbox; function delete (listbox, index, #key end) => listbox; function insert (listbox, index, #rest elements) => listbox; function get-all (listbox) => result :: <string>; function get-elements (listbox, index, #key end) => result :: <string>; function scan-mark (listbox, #rest coords) => listbox; function scan-dragto (listbox, #rest coords) => listbox; function select-adjust (listbox, index) => listbox; function select-clear (listbox) => listbox; function select-from (listbox, index) => listbox; function select-to (listbox, index) => listbox;
function activate-entry (menu, index) => menu; function delete (menu, index, #key end) => menu; function disable-entry (menu, index) => menu; function enable-entry (menu, index) => menu; function configure-entry (menu, index, #rest options) => menu; function entry-configuration (menu, index) => result :: <sequence>; function invoke-entry (menu, index) => result; function post (menu, x, y) => (); function unpost (menu) => (); function yposition-entry (menu, index) => result :: <integer>; function add-command (menu, #key state, command, label) => (); function add-checkbutton (menu, #key variable, label) => (); function add-radiobutton (menu, #key variable, value, label) => (); function add-cascade (menu, #key menu, label) => (); function add-separator (menu) => ();Note that if you wish to assocaiate a menu with a menubutton, you must create it "in:" that button.
Supports no operations.
function get-value (scale) => result :: <integer>; function set-value (scale, value) => scale;
function scroll (widget, #key, #all-keys) => scrollbar; function get-units (scrollbar) => (#rest units :: <integer>); function set-units (scrollbar, #rest Units) => scrollbar;The "scroll" function exists to reduce the inefficiency inherent in "standard" method of connecting widgets to scrollbars. This function creates a new scrollbar with the given orientation (i.e. "vertical" or "horizontal") and connects it via lower level mechanisms to the given widget. All of the applicable creation and packing options for scrollbars are accepted.
function yview (text, index) => text; function delete (text, index, #key end) => text; function insert (text, index, #rest elements) => text; function get-all (text) => result :: <string>; function get-elements (text, index, #key end) => result :: <string>; function scan-mark (text, #rest coords) => text; function scan-dragto (text, #rest coords) => text; function select-adjust (text, index) => text; function select-clear (text) => text; function select-from (text, index) => text; function select-to (text, index) => text;
Supports no operations.
These operations will hopefully be described at greater length in future editions of this document, but for now you are encouraged to check the Tcl/Tk manual for further information.
<Text-index>s represent an "absolute" index within a <text> object. They have two slots -- "line" and "character". "Make" on <text-index> requires a "line:" keyword and accepts an optional "character:" keyword (which defaults to 0). Lines numbers start at 1, while characters start at 0, so you could
specify the first character of a <text> by calling make(<text-index>, line: 1, character: 0)The following functions create and manipulate <text-index>s:
function text-at (line, character) => result :: <text-index>; function line-end (line) => result :: <string>; method as (cls == <text-index>, value :: <string>) => string method as (cls == <string>, value :: <text-index>) => text-indexThe "text-at" function provides a convenient shorthand for making <text-index>s. You might, for example replace the above "make" call with
text-at(1, 0);"Line-end" creates a reference to the last character of the named line.
If you make a <text-mark> with the name of a "standard" Tcl/Tk mark, such as "end" or "insert", then the new object will pick up the values already associated with the Tcl/Tk mark.
The following functions create or manipulate <text-mark>s:
method as (cls == <string>, object :: <text-mark>) => string method as (cls == <text-index>, mark :: <text-mark>) => text-index method marks (text :: <text>) => result :: <sequence>;The "marks" function returns a sequence containing all of the marks within a given <text>. This will include both the "standard" Tcl/Tk marks and any that you have created. These will not necessarily be "==" to the original objects.
<Text-tag>s are created within a particular <text> via "make", which requires an "in:" keyword. Optional keywords include "name:", "bgstipple:", "fgstipple:", "font:", and "underline:". Although tags are not widgets, they support the "configure", "configuration", and "bind" functions. You may add ranges of characters to <text-tag>s via "add-tag". A complete list of functions upon <text-tag>s follows:
function configure (tag, #rest options) => tag; function configuration (tag, index) => result :: <sequence>; function bind (tag, event :: <string>, command) => tag; method as (cls == <string>, object :: <text-tag>) => string; function add-tag (tag, #key start, end: last) => tag; function remove-tag (tag, #key start, end: last) => tag; function tags (text :: <text>) => result :: <sequence>; function delete-tag (tag) => (); function raise-tag (tag, #key past :: <text-tag>) => (); function lower-tag (tag, #key past :: <text-tag>) => (); function next-range (tag, #key start, end) => result :: false-or(<pair>);
The following functions operate upon canvas items:
function configure (item, #rest options) => item; function configuration (item, index) => result :: <sequence>; function bind (item, event :: <string>, command) => item; function delete-item (item) => (); function raise-item (item, #key past :: <canvas-item>) => (); function lower-item (item, #key past :: <canvas-item>) => (); function move-item (item, x :: <object>, y :: <object>) => (); function scale-item (item, x-origin, y-origin, x-scale, y-scale) => (); function item-coords (item) => result :: <sequence>; function item-coords-setter (value :: <sequence>, item) => (); function item-type (item) => result :: <string>;
The following functions are exported:
You may wish to add methods to this function for any new dylan types you add.
You should be careful to include spaces between distinct tokens in the input, since "put-tk-line" will cheerfully combine several strings into a single Tk token.
You may need to call "tk-as", "tk-unquote", or "parse-tk-list" on the result depending upon the expected result type.