Skip to content


[module] bt-gui-lolevel

Low level GUI abstractions and helper procedures.


[syntax] tk/bind*

Thread-safe version of tk/bind. Wraps the procedure PROC in a thunk that is safe to execute as a callback from Tk.

[procedure] (bind-key WIDGET GROUP ACTION PROC)

Bind the keypress event for WIDGET to PROC. ACTION must be a mapping listed in the group GROUP of the active keymap.

[procedure] (recolor-png FILENAME COLOR)

[procedure] (tk/icon FILENAME #!optional (ICON-COLOR (colors 'text)))

Create a tk image resource from a given PNG file.

[procedure] (make-separator PARENT ORIENT)

[procedure] (add-size-grip)


This section provides abstractions over Tk dialogues and pop-ups. tk/safe-dialogue is potentially the most useful entry points for creating native dialogues from user code.

[procedure] (tk/safe-dialogue TYPE . ARGS)

Used to provide safe variants of tk/message-box, tk/get-open-file, and tk/get-save-file that block the main application window while the pop-up is alive. This is a work-around for tk dialogue procedures getting stuck once they lose focus. tk-with-lock does not help in these cases.

[procedure] (tk/message-box* . ARGS)

Crash-safe variant of tk/message-box.

[procedure] (tk/get-open-file* . ARGS)

Crash-safe variant of tk/get-open-file.

[procedure] (tk/get-save-file* . ARGS)

Crash-safe variant of tk/get-save-file.

[procedure] (about-message)

Display the "About Bintracker" message.

[procedure] (exit-with-unsaved-changes-dialog EXIT-OR-CLOSING)

Display a message box that asks the user whether to save unsaved changes before exiting or closing. EXIT-OR-CLOSING should be the string "exit" or "closing", respectively.

Widget Style

[procedure] (default-theme-generator)

Generates the default Bintracker Tk theme and saves it as resources/bt-theme.tcl.

[procedure] (update-ttk-style)

Configure ttk widget styles.

[record] menu

[constructor] (make-menu #!key (WIDGET (tk 'create-widget 'menu)) (ITEMS '()))
[predicate] menu?
implementation: defstruct

field getter setter default type
widget menu-widget menu-widget-set! (tk 'create-widget 'menu) procedure
items menu-items menu-items-set! '() list

[procedure] (add-menu-item! MENU ITEM-SPEC)

Destructively add an item to menu-struct menu according to item-spec. item-spec must be a list containing either - ('separator) - ('command id label underline accelerator command) - ('submenu id label underline items-list) where id is a unique identifier symbol; label and underline are the name that will be shown in the menu for this item, and its underline position; accelerator is a string naming a keyboard shortcut for the item, command is a procedure to be associated with the item, and items-list is a list of item-specs.

[procedure] (construct-menu ITEMS)


[procedure] (disable-keyboard-traversal)

Disable automatic keyboard traversal. Needed because it messes with key binding involving Tab.

[procedure] (create-virtual-events)

Create default virtual events for Bintracker. This procedure only needs to be called on startup, or after updating key bindings.

[procedure] (reverse-binding-eval-order WIDGET)

Reverse the evaluation order for tk bindings, so that global bindings are evaluated before the local bindings of WIDGET. This is necessary to prevent keypresses that are handled globally being passed through to the widget.


TextGrids are Tk Text widgets with default bindings removed and/or replaced with Bintracker-specific bindings. TextGrids form the basis of Bintrackers metawidget, which is used to display sets of blocks or order lists. A number of abstractions are provided to facilitate this.

[procedure] (textgrid-configure-tags TG)

Configure TextGrid widget tags.

[procedure] (textgrid-do-tags METHOD TG TAGS FIRST-ROW #!optional (FIRST-COL 0) (LAST-COL 'end) (LAST-ROW #f))

Abstraction over Tk's textwidget tag add command. Contrary to Tk's convention, ROW uses 0-based indexing. TAGS may be a single tag, or a list of tags.

[procedure] (textgrid-add-tags . ARGS)

[procedure] (textgrid-remove-tags . ARGS)

[procedure] (textgrid-remove-tags-globally TG TAGS)

[procedure] (textgrid-position->tk-index ROW CHAR)

Convert the row, char arguments into a Tk Text index string. row is adjusted from 0-based indexing to 1-based indexing.

[procedure] (textgrid-create-basic PARENT)

Create a TextGrid as slave of the Tk widget parent. Returns a Tk Text widget with class bindings removed.

[procedure] (textgrid-create PARENT)

UI Element Base Classes

[class] <ui-element>

slot initform accessor
initialized #f
parent tk ui-parent
packing-args '()
box ui-box
children '() ui-children

A <ui-element> represents a GUI metawidget consisting of one or more Tk widgets. The metawidget is self-contained, meaning all it's child widgets are wrapped in a ttk::frame. A <ui-element> instance may contain other <ui-elements> as child elements.

Any instance of <ui-element> or a derived class contains the following fields:

  • setup - an expression specifying how to construct the UI element. Details depend on the specific class type of the element. For standard <ui-element>s, this is the only mandatory field. Provides a reader named ui-setup.

  • parent - the Tk parent widget, typically a tk::frame. Defaults to tk if not specified. Provides an accessor named ui-parent.

  • packing-args - additional arguments that are passed to tk/pack when the UI element's main widget container is packed to the display.

  • children - an alist of child UI elements, where keys are symbols and values are instances of <ui-element> or a descendant class. Children are derived automatically from the setup field, so the user normally does not need to interact with the children field directly. Provides an accessor named ui-children.

The generic procedures ui-show, ui-hide, and ui-ref are implemented for all UI element classes. UI elements commonly also provide ui-set-state and ui-set-callbacks methods.

To implement your own custom UI elements, you should create a class that inherits from <ui-element> or one of its descendants. You probably want to define at least the initialize-instance method for your class, which should be an after: method. Note that <ui-element>'s constructor does not initialize the child elements. ui-show, however, will recursively apply ui-show on an <ui-element>. Therefore the children slot must not contain anything but named instances of <ui-element>, unless you override ui-show with your own primary method. The recommended way is to add new slots to your derived class for any custom widgets not derived from <ui-element>.

[method] (initialize-instance after: (ELEM <ui-element>) )

[method] (ui-show primary: (ELEM <ui-element>) )
Map the GUI element to the display.

[method] (ui-hide primary: (ELEM <ui-element>) )
Remove the GUI element from the display.

[method] (ui-where primary: (ELEM <ui-element>) )

[method] (ui-what primary: (ELEM <ui-element>) )

[method] (ui-destroy primary: (ELEM <ui-element>) )
Remove the GUI element ELEM from the display and destroy it. Destroying an element will recursively call ui-destroy on ELEM's child elements, before destroying its associated Tk widgets. You cannot resurrect ELEM after calling this method.

[method] (ui-ref primary: (ELEM <ui-element>) CHILD-ELEMENT)
Returns ELEMs child UI element with the identifier CHILD-ELEMENT. The requested element may be a direct descendant of ELEM, or an indirect descendant in the tree of UI elements represented by ELEM.

[class] <ui-wrapper>

inherits from: <ui-element>

slot initform
yscroll #f
orient 'horizontal
packing-args '(padx: 4 pady: 4 side: top fill: x)

Class-based container for one or more Tk widgets. This is essentially an adapter for using raw Tk widgets from within Bintracker's class-based UI system. Create instances as follows:

(make <ui-wrapper>
      'setup ((ID1 WIDGET-TYPE1 [ARGS...]) ...)
      ['orient ORIENT]
      ['yscroll YS])

where ID1 is a unique child element identifier, WIDGET-TYPE1 is the name of a Tk widget element, and ARGS... are the arguments passed to the widget's consructor. ORIENT specifies the orientation in which the widgets will be packed. It must be either 'horizontal or 'vertical, defaults to 'horizontal. YS may be a boolean. If #t, setup may contain only a single child element, and ORIENT must be 'horizontal. A vertical scrollbar will be added for the child element.

[method] (initialize-instance after: (ELEM <ui-wrapper>) )

[method] (ui-show primary: (ELEM <ui-wrapper>) )

[method] (ui-ref primary: (ELEM <ui-wrapper>) CHILD-ELEMENT)

[class] <ui-modeline>

inherits from: <ui-element>

slot initform
packing-args '(expand: 0 fill: x side: bottom)

[method] (initialize-instance after: (BUF <ui-modeline>) )
A modeline (aka status bar) widget. Create instances with

(make <ui-modeline> 'setup ((ID TEXT [COLOR]) ...))

where ID is a unique identifier of a modeline segment, and TEXT is the string that will be displayed in the modeline segment. An empty string means the segment is not displayed. If COLOR is given, it must be an integer referencing one of the application colors text-1 ... text-7. If COLOR is omitted, the color text will be used.

[method] (ui-show before: (BUF <ui-modeline>) )

Set the TEXT string of the modeline segment identifier SEGMENT.

[class] <ui-setting>

inherits from: <ui-element>

slot initform
packing-args '(side: left)

A class representing a labelled Tk spinbox. Create instances with

(make <ui-setting>
      'parent PARENT

where PARENT is the parent Tk widget, LABEL is the text of the label, INFO is a short description of the element's function, DEFAULT-VAR is a symbol denoting an entry in (settings), FROM and TO are integers describing the range of permitted values, and CALLBACK may optionally a procedure of no arguments that will be invoked when the user selects a new value.

[method] (initialize-instance after: (BUF <ui-setting>) )

[method] (ui-set-state primary: (BUF <ui-setting>) STATE)
Set the state of the UI element buf. state can be either 'disabled or 'enabled.

[class] <ui-settings-group>

inherits from: <ui-element>

slot initform
packing-args '(expand: 0 fill: x)

A wrapper for one or more <ui-setting>s. Create instances with

(make <ui-settings-group> 'setup '((ID1 CHILD-SPEC ...) ...))

where ID1 is a unique child element identifier, and CHILD-SPEC ... are the remaining arguments that will be passed to <ui-setting>'s constructor the 'setup argument.

[method] (initialize-instance after: (BUF <ui-settings-group>) )

[method] (ui-set-state primary: (BUF <ui-settings-group>) STATE)
Enable or disable BUF. STATE must be either 'enabled or 'disabled.

[class] <ui-button-group>

inherits from: <ui-element>

slot initform
packing-args '(expand: 0 side: left)
orient 'horizontal

A class representing a group of button widgets. Create instances with

(make <ui-button-group> 'parent PARENT
      'setup '((ID INFO ICON-FILE [INIT-STATE]) ...))

where PARENT is the parent Tk widget, ID is a unique identifier, INFO is a string of text to be displayed in the status bar when the user hovers the button, ICON-FILE is the name of a file in resources/icons/. You may optionally set the initial state of the button (enabled/disabled) by specifying INIT-STATE.

[method] (initialize-instance after: (BUF <ui-button-group>) )

Enable or disable BUF or one of it's child elements. STATE must be either 'enabled or 'disabled. When passing a BUTTON-ID is specified, only the corresponding child element's state changes, otherwise, the change affects all buttons in the group.

Set callback procedures for buttons in the button group. callbacks must be a list constructed as follows:

((ID THUNK) ...)

where ID is a button identifier, and THUNK is a callback procedure that takes no arguments. Optionally, ENTER-BINDING may be a callback procedure with no arguments that will be invoked when the user starts hovering over the button with the mouse, and LEAVE-BINDING may be a callback procedure with no arguments that is invoked when the mouse leaves the button area. You would typically use this to display some information about the button in a modeline.

[class] <ui-toolbar>

inherits from: <ui-element>

slot initform
packing-args '(expand: 0 fill: x)

A class representing a toolbar metawidget, consisting of <ui-button-group>s. Create instances with

(make <ui-toolbar> 'parent PARENT
      'setup '((ID1 BUTTON-SPEC1 ...) ...))

where PARENT is the parent Tk widget, ID1 is a unique identifier, and BUTTON-SPEC1 is a setup expression passed to .

[method] (initialize-instance after: (BUF <ui-toolbar>) )

Set callback procedures for buttons in the toolbar. callbacks must be a list constructed as follows:


where ID is a button group identifier and BUTTON-GROUP-CALLBACK-SPEC is a callback specification as required by the ui-set-callbacks method of <ui-button-group>.

[class] <ui-buffer-decorations>

slot initform
toolbar #f
settings-bar #f
modeline #f

An auxiliary class used to add toolbars, settings-bars, and modelines (status bars) to classes derived from <ui-element>.

Classes inheriting from this must initialize the slots to an instance of <ui-toolbar>, <ui-settings-bar>, and/or <ui-modeline>, for the toolbar, settings-bar, and modeline slots, respectively.

You can then call ui-show-decorations on instances of your derived class to map the decorations to a chosen parent Tk frame).

Pack the decorations to the PARENT Tk frame window (usually (ui-buf) of the class that inherits from this and <ui-element>).

[class] <ui-selectable>

slot initform accessor
selection #f ui-selection

[class] <ui-multibuffer>

inherits from: <ui-element><ui-buffer-decorations>

slot initform
packing-args '(expand: 1 fill: both)
orient 'vertical
setup '()

A class representing a container widget that wraps multiple resizable ui-buffers in a ttk panedwindow.

Create instances with

(make <ui-multibuffer> 'parent PARENT
      'setup ((ID1 VISIBLE WEIGHT CHILD-SPEC ...) ...))

where PARENT is the parent Tk widget (defaults to tk), ID1 is a unique identifier for a child buffer, VISIBLE is a boolean specifying if the child widget should initially be mapped to the display, WEIGHT is an integer specifying how large the child buffer should be in relation to the remaining child buffers, and CHILD-SPEC ... is the name of a UI buffer class, followed by the arguments that shall be passed to make when creating the child buffer instance.

The optional ORIENT argument specifies the orientation of the metabuffer; it shall be one of the symbols 'vertical or 'horizontal. By default, metabuffers are oriented vertically, meaning new child buffers will be added below the current ones.

The state slot contains an alist with the child identifiers as keys. alist-ref will return a list in the form (INDEX VISIBLE WEIGHT), where INDEX is an integer representing the position of the child element in the multibuffer, VISIBLE is #t if the child element is currently controlled by the display manager and #f otherwise, and WEIGHT is an integer specifying the initial size of the child element in relation to the other children (not taking into account resizes by the user),

[method] (initialize-instance after: (BUF <ui-multibuffer>) )

[method] (multibuffer-active+sorted-children primary: (BUF <ui-multibuffer>) )
Returns the actively managed children of BUF sorted by position.

[method] (ui-show primary: (BUF <ui-multibuffer>) )

[method] (multibuffer-show primary: (BUF <ui-multibuffer>) CHILD)
Map the child element CHILD to the display. Does nothing if CHILD is already visible.

[method] (multibuffer-hide primary: (BUF <ui-multibuffer>) CHILD)
Remove the child element CHILD from the display. Does nothing if CHILD is currently not hidden. You can add back CHILD at a later point with multibuffer-show. If CHILD is no longer needed at all, use multibuffer-destroy instead.

[method] (multibuffer-delete primary: (BUF <ui-multibuffer>) CHILD)
Remove the child element CHILD from the multibuffer display and delete it. If you just want to remove the child from the display, use multibuffer-hide instead.

Add a new child buffer. CHILD-SPEC shall have the same form as the elements in the 'setup argument to (make <ui-multibuffer ...). The new child buffer will be added before the child named BEFORE, or at the end if BEFORE is not specified.

[class] <ui-buffer>

inherits from: <ui-element><ui-buffer-decorations>

slot initform
title ""
default-state 'expanded
collapse-proc #f
expand-proc #f
collapsible #t

This class commonly acts as a superclass for UI classes that represent user data. <ui-buffer>'s are collapsible. This means child elements are wrapped in a frame that the user can fold and unfold by clicking a button, or through a key binding. ` and many of the module display related widgets are based on this class.

The constructor of this class does not evaluate 'setup expressions, so derived classes should provide their own setup reader. A plain can be constructed with

(make <ui-buffer> 'children ((ID1 . ELEMENT1) ...))

where ID is a unique child element identifier, and ELEMENT1 is an instance of a <ui-element>.

[method] (initialize-instance after: (BUF <ui-buffer>) )

[method] (ui-show after: (BUF <ui-buffer>) )

[method] (ui-collapse primary: (BUF <ui-buffer>) )

[method] (ui-expand primary: (BUF <ui-buffer>) )

[class] <ui-dialog>

slot initform accessor
initialized #f
visible #f
parent tk ui-parent
packing-args '()
box ui-box
title ""
initializers (make-hooks)
finalizers (make-hooks)
children '() ui-children

A class representing a popup dialog. Dialogs are automatically constructed with two buttons labelled "Confirm" and "Cancel", and <Escape> and <Return> keypress events are always bound. Construct instances with:

(make <ui-dialog>
      ['title TITLE]
      ['children CHILD-SPECS]
      ['initializers INIT-HOOKS]
      ['finalizers FINAL-HOOKS]
      ['parent PARENT])

TITLE may be a string that will be displayed as the dialog window name.

CHILDREN may be an associative list of named child elements, which must be <ui-element>s.

Widgets in a dialog are not created until the dialog is shown. This means you cannot bind events or apply procedures to child elements of the dialog directly after initialization. To apply bindings and/or procedures, provide a hook set as INIT-HOOKS.

FINALIZERS may be a hook set that will be executed when the user clicks the "Confirm" button or hits the Return key. Procedures in the hook set must be stubs, ie. they may not take any arguments.

PARENT may be a Tk toplevel window. The dialog window acts as a transient on the PARENT. PARENT defaults to the root window tk. You normally do not need to set this explicitly, except when creating dialogs acting on behalf of other dialogs.

As long as the dialog is not destroyed (by calling ui-destroy on it or manually destroying its ui-box), it preserves state between invocations.

Note that <ui-dialog> is not a descendant of <ui-element>. In practice this hardly matters, as <ui-dialog> supports all of the basic methods defined on <ui-element>. However, note the caveat regarding applying bindings and procedures to sub-widgets above.

[method] (ui-hide primary: (D <ui-dialog>) )

[method] (ui-destroy primary: (D <ui-dialog>) )

[method] (ui-ref primary: (ELEM <ui-dialog>) CHILD-ELEMENT)