SETL's Interactive Graphical Interface; Sockets and Internet-Programming Basics
SETL's Interactive Graphical Interface, which is built upon the powerful and widely used Tk widget library developed by John Osterhout, provides a full set of interactive graphical widgets, including buttons, menus, editable text entry and display areas of one or more lines, sliders, listboxes, 'labels' and 'messages' for the display of non-editable text, sliders, scrollbars, and 'canvases' in which a variety of geometric objects can be drawn and repositioned. Checkbuttons and radiobuttons are also supported. The geometric objects available within canvases are rectangles, ovals, polygons, lines (open multipoint spline curves), arcs, bitmaps, images, and embedded text. Additionally, any other widget, including subcanvases, can drawn to a canvas, so that canvases can be structured as collages of basic geometric objects and subcanvases.
All widgets are sensitive to a wide variety of events, including mouse clicks and mouse motion, making it easy to support many kinds of draggable objects. The text area widget is particularly powerful, in that it supports fully fonted and hotworded text, and allows embedding of images, and, indeed, of any other widget.
The facilities provided allow any number of self-standing windows, called 'toplevels', to be opened, displayed or temporarily hidden, positioned, and resized. Within these windows one can hierarchically arrange rectangular 'frames', subframes, canvases, and text areas, with other widgets placed in them. The detailed arrangement of all these items can either be calculated automatically (allowing for easy, and often adequate, response to window resizing), or can be brought under detailed program control. The two forms of automatic placement provided are called the 'pack geometry manager' (which tries to fit items as close as it can to a specified frame edge), and the 'grid geometry manager' (which places elements in row-and-column arrays.) The third and last of 'geometry manager', provided, the 'place geometry manager', allows detailed program control of placement.
To use the interactive graphical interface, one simply includes the declaration
use tkw;
in one's program or package; this loads the tkw class, supplied with SETL, which provides all the interactive graphical interface facilities. Use of the interactive graphical interface is then initiated by executing the single statement
master_window_name := tkw();
which performs all necessary initializations and opens a first toplevel window. This is followed by a block of code which creates all desired additional windows and widgets which are to open immediately.
Once all initially desired windows and widgets have been created, event-driven execution of the interface system, and of the SETL system backing it up, is initiated by making the call
Tk.mainloop();
(Other windows and widgets can then be created dynamically whenever desired). This enters Tk's main loop, to which the SETL interpreter is subsequently subordinate, in the sense that it will only receive control via event-driven callbacks from Tk set up by the initial 'Setup' code.
Here is a small "Hello World" example, which simply displays "Hello World" inside a window in large-size red type. We will shortly detail the all the operations and conventions which it uses.
program test; -- SETL interactive interface example 1 use tkw; -- use the standard graphical interface package Tk := tkw(); -- create the Tk interpreter txt := Tk("text","20,3"); txt("side") := "top"; -- create a 'text area' widget, 20 characters by 3 lines in size txt(OM) := "Hello World"; -- insert text into it txt("font,foreground") := "{Times 48},red"; -- set the text font and color Tk.mainloop(); -- enter the main Tk loop end test;
'Toplevel' widgets are self-standing windows of whatever kinds are available in the system under which SETL and Tk are running. They are created by commands of the form
toplevel_name := master_window_name("toplevel","initial_width,initial_height");
where 'tkw_name' is the name of the master widget object. (This is generally the object 'Tk' created by the initiating call
Tk := tkw(); -- create the Tk interpreter
All other widgets are then created and arranged hierarchically within the toplevel' widgets opened. Each toplevel can have its own menubar.
'Frames' are rectangular areas within toplevel windows or other frames, within which other widgets can be arranged. They are created by commands of the form
frame_name := parent_frame_or_window("frame","initial_width,initial_height");
Other widgets can then be created and arranged hierarchically within such frames.
A frame or other widget created within a toplevel window or another frame only becomes visible when it has been assigned a place, either explicitly or by defining the 'geometry manager' responsible for placing it. For example, once can write
frame_name("side") := "top";
This use of "side" implies that the frame is to be placed within its parent P by the 'pack geometry manager', which in our example is instructed to position the frame toward the top of P. Instead of "top", one could use "bottom", "left", or "right".
The same rules apply to other widgets created within frames. Once such a widget has been created, it can be made visible by positioning it within its parent frame, e.g. by writing
widget_name("side") := "top";
A widget with a given parent is created by writing
widget_name := parent_frame(widget_type,main_parameter);
for example
button_name := parent_frame("button","Please Click Me Right Now!");
as seen in this example, the 'main parameter' used when creating a button is the button text. The following table shows the main parameters used in creating other kinds of widgets:
button parent("button",button_text) menu parent("menu",descriptor) -- the structure of 'descriptor' is detailed below one-line text parent("entry",width_in_characters) text area parent("text",width_in_characters_and_height_in_lines) slider parent("scale",least_value_and_greatest_value) listbox parent("listbox",number_of_items) label parent("label",label_text) message parent("message",message_text_and_width) scrollbar parent("scrollbar",orientation_and_width) -- e.g. "horizontal,16" or "vertical,10" canvas parent("canvas",width_and_height) subframe parent("frame",width_and_height)Examples are
one-line text parent("entry",50) text area parent("text","50,10") slider parent("scale","-100,100") listbox parent("listbox",15) canvas parent("canvas","640,480") subframe parent("frame","320,240")The second parameter supplied can either be a string, an integer, or a tuple of appropriate length. For example, the preceding examples can instead be written as
one-line text parent("entry","50") text area parent("text",[50,10]) slider parent("scale",[-100,100]) listbox parent("listbox",15) canvas parent("canvas",[640,480]) subframe parent("frame",[320,240])Each kind of widget has a variety of additional attributes which can be set to define various details of the widget's geometry and behavior. For example, the attributes available for button widgets are:
text - button caption state - 'normal' or 'disabled' (if disabled, text is greyed out) width - button width, in characters height - button height, in characters borderwidth - button border width, in pixels image - image to display instead of text caption font - text font anchor - caption anchor point: n,ne,e,se,s,sw,w,nw, center justify - caption justification in its rectangle: - left, right, or center foreground - text color background - background color activeforeground - text color when mouse is pressed over button activebackground - background color when mouse is pressed over button disabledforeground - text color when button is disabled cursor - cursor to display when mouse is over button default - if true, button is emphasized padx,pady - size of extra padding space around text highlightthickness - thickness of additional border used to indicate focus highlightbackground - color of additional border when widget - does not have focus highlightcolor - color of additional border when widget has focus takefocus - command to be executed when widget receives focus via tab textvariable - if non-null, names a Tk variable holding - the button's string underline - index of text character to underline wraplength - maximum line length before caption text wrapsComprehensive lists of attributes for all the other kinds of widgets appear later in this chapter. Note that not every Tk implementation actually supports all the effects of all widget attributes. In such situations, setting unsupported widget attributes simply has no effect.
Widget attributes are set by writing assignments of the form
widget(attribute_list) := attribute_value_list;
An example is
button_obj("foreground,background,cursor") := "red,yellow,crosshair";
or equivalently
button_obj("foreground,background,cursor") := "red;yellow;crosshair";
or
button_obj("foreground,background,cursor") := ["red","yellow","crosshair"];
as these examples indicate, the parameter attribute_list can be either a single attribute name or a comma-separated list of attribute names, and the attribute_value_list can be either (i) a single attribute value, or (ii) a comma-separated list of attribute values, given as a string, or (iii) a semicolon-separated list of attribute values, given as a string, or (iv) a tuple of attribute values. Also, whenever an integer-valued attribute appears, it can be given either as a SETL integer, or as the corresponding string. Plainly, assignment forms like those seen above facilitate simultaneous modification of multiple widget attributes.
'Principal' Attributes. As a matter of convenience, many kinds of widgets w are considered to have one 'principal' attribute, which can be retrieved/set by writing
x := w(OM) and w(OM) := x; respectively.
This simply makes it unnecessary to remember any more detailed name for the attribute. The 'principal' attributes available in this way are as follows:
Widget Type Principal Attribute TextLine String contents Label String contents Message String contents Text String contents Slider Numerical slider value Listbox List of all currently selected items (Read only) Menubutton The associated menu (Write only) Canvas Tuple of all canvas items (Read only) Toplevel Window title
Attributes available for all widgets. The following attributes are available for all widgets (some are read-only):
children tuple of widgets which are children of given widget showing true if widget is currently showing on screen, false otherwise manager geometry manager for window: pack, place, grid, canvas, or text parent parent widget of given widget rect rectangle of given widget: [r,t,l,b] wincoords corner of toplevel window containing widget toplevel toplevel window containing widget mouse current position of the mouse screendepth number of bits used to store screen colors screensize size of screen, in pixels screenmm size of screen, in millimeters
Items placed by 'packing'. The positions and sizes of items 'packed' into a frame are calculated using a set of placement attributes associated with each such object; side, padx, pady, ipadx, ipady, expand, fill, and anchor. All objects to be packed into a frame (or window) appear in an ordered 'packing list' associated with the frame. Objects appear on such a list when their "side" attribute is set, and can be removed either by setting this attribute to OM or by setting an attribute which transfers control of the object to one of the other geometry managers. The packing list can also be manipulated by setting the an object's 'in' attribute (which can move it to another frame or window), by making assignments to its 'after' or 'before' attribute (which moves it on the packing list), or by setting the 'children' attribute of the frame originally containing the object. Aside from this, the general rule is that
(i) objects obj appear on the packing list of their parent frame or window, that is, the frame or window fw using which the object was created by a statement like
obj := parent_frame(widget_type,main_parameter);
(ii) unless moved by assignments to their 'after' or 'before' attributes, objects appear on a frame's packing list in the order in which the operations
obj("side") := "left"; -- or "right", "top", or "bottom"
that put them there are executed.
The detailed placement within a frame F of all the objects on F's packing list is worked out by processing them in the order of that list. The algorithm used is roughly as follows. Let the objects to be packed be o1,o2,...olast. If the first few objects being packed are packed toward the left or right edge, break the list just before the first oj which is packed toward the top or bottom, then subsequently before the first oj which is packed toward the left or right, etc. This breaks the packing list into runs of objects, each consisting of objects which are all packed either in the vertical or all in the horizontal direction. Recursively pack oj,...olast into a large enough rectangle R; then pack o1,o2,...o(j - 1) and R horizontally into the frame F, placing all elements packed to the left (resp. right) in an initial (resp. final) sequence, with R in the middle. The frame F can then shrink to the minimum size needed to hold all the elements packed into it.
Note that if, in our example, the objects o1,o2,...o(j - 1) are of different heights, there may be unoccupied space above or below them. If this is the case each object will be placed in the center of the space assigned to it, except that (i) it can be moved to another position in this space if its 'anchor' attribute is set (to one of the positions n, s, e, w, ne, se, nw, sw), and (ii) it can be enlarged to fill the available space if its 'fill' attribute, which designates the directions in the object is allowed to expand (none, x, y, or both), is appropriately set.
If a rectangle like R is packed into the middle of a run of objects taller (or wider) than itself, then more space may be available to R than it would occupy if minimized. In such cases, if the 'expand' attribute of some of the objects packed into R has been set to true, the additional space available to R will be divided between them, each such object being placed either in the center of its available space, or at the position (n, s, e, w, ne, se, nw, sw, or center) determined by its 'anchor' attribute.
Sometimes one wants to hold some of the intermediate frames of a placement hierarchy at their stated sizes rather than allowing them to shrink to fit their contents. This can be done by setting their 'adjust' attribute to 'false'.
Objects are placed by default into their parent frame. This default action can be over-ridden by setting their 'in' attribute to ay other window descended from the same ancestral toplevel window (but no other, since an object always can become invisible when this ancestral window does.
Additional internal space, into which an object will always expand, can be reserved for it by setting its ipadx and ipady attributes to the desired number of pixels.
The above remarks should clarify the meaning of the packing-control attributes listed below. However, since the action of the packing algorithm is not always intuitive, its is generally better to control packing explicitly by introducing as may intermediate frames as necessary to make packing at any one level of the hierarchy of frames introduced run either horizontally or vertically. (In any case, the packing algorithm does this implicitly.)
The attributes involved in widget placement by 'packing' are:
side - edge (top, bottom, left, right) toward which widget is packed in - widget, other than parent, in which this widget should be packed - Note: can only pack into frame belonging to same toplevel after - widget that this should follow in packing order before - widget that this should precede in packing order anchor - position in available space that item should occupy: - (n, s, e, w, ne, se, nw, sw, or center) expand - if true, widget will claim available space - in its packing direction fill - none, x, y, or both: widget fills available space - in that direction ipadx,ipady - size of extra internal padding space within item packed children - ordered list of items packed within a frame adjust - if true, frame will adjust to minimum size required for children
The following example illustrates the use of placement by packing.
program test; -- SETL interactive interface example 1 use tkw; -- use the main widget class Tk := tkw(); Tk(OM) := "Example 1"; -- create the Tk interpreter msg := Tk("message","A first message"); msg("side") := "top"; -- create and place a first message but := Tk("button","You can click this"); but("side,fill") := "top,y"; -- create and place a first button msg2 := Tk("message","A second message"); msg2("side") := "bottom"; -- create and place a second message but2 := Tk("button","Or click this"); but2("side,fill") := "bottom,both"; -- create and place a second button Tk.mainloop(); -- enter the Tk main loop end test;We now give a series of small examples illustrating the use of the 'pack'-related attributes listed above. In all the examples, two thin colored frames are inserted into a window as 'supports' to keep it from shrinking to a smaller size dictated by its contents (this will be discussed below), and one or two buttons are inserted into the remaining space. Our first example is
program test; -- SETL interactive interface example 1 use tkw; -- use the main widget class Tk := tkw(); Tk(OM) := "Example 1"; -- create the Tk interpreter frtop := Tk("frame","200,10"); -- put in two frames as 'supports' frtop("side") := "top"; frtop("background") := "yellow"; fleft := Tk("frame","10,190"); fleft("side") := "left"; fleft("background") := "red"; but := Tk("button","Scan to (4,4)"); but("side") := "left"; -- create and pack a button but("pack,anchor") := "nw"; print(but("pack")); -- set some of its packing attributes, print all of them Tk.mainloop(); -- enter the Tk main loop end test;Note the placement of the packed button, and the fact that it fills only a small part of the available space. In this and our next few examples, we use the expression
obj("pack")
to retrieve the list of all widgets packed into a given object. The output produced is
{["in", "."], ["padx", "0"], ["ipadx", "0"], ["expand", "0"], ["side", "left"], ["fill", "none"], ["pady", "0"], ["ipady", "0"], ["anchor", "nw"]} [frame:.w1, frame:.w2, button:.w3]Changing the 'side' and the 'anchor' attributes leads to the alternate placement generated by the following example.
program test; -- SETL interactive interface example 1 use tkw; -- use the main widget class Tk := tkw(); Tk(OM) := "Example 1"; -- create the Tk interpreter frtop := Tk("frame","200,10"); -- put in two frames as 'supports' frtop("side") := "top"; frtop("background") := "yellow"; fleft := Tk("frame","10,190"); fleft("side") := "left"; fleft("background") := "red"; but := Tk("button","Scan to (4,4)"); but("side") := "right"; -- create and pack a button but("pack,anchor") := "sw"; print(but("pack")); -- set some of its packing attributes, print all of them print(Tk("children")); -- print the list of items packed into the window Tk.mainloop(); -- enter the Tk main loop end test;By setting the 'fill' attribute to 'y' we cause the button to expand vertically into all the space available to it.
program test; -- SETL interactive interface example 1 use tkw; -- use the main widget class Tk := tkw(); Tk(OM) := "Example 1"; -- create the Tk interpreter frtop := Tk("frame","200,10"); -- put in two frames as 'supports' frtop("side") := "top"; frtop("background") := "yellow"; fleft := Tk("frame","10,190"); fleft("side") := "left"; fleft("background") := "red"; but := Tk("button","Scan to (4,4)"); but("side") := "left"; -- create and pack a button but("pack,anchor,fill") := "nw,y"; print(but("pack")); -- set some of its packing attributes, print all of them Tk.mainloop(); -- enter the Tk main loop end test;In our next example we introduce a second button and set the 'fill' attribute of both buttons to 'both', but the 'expand' attribute of only the first button. This causes the first, but not the second button, to expand in its horizontal packing direction, to fill all available space. Both buttons then expand vertically, so all the frame space is filled, but the first button is wider.
program test; -- SETL interactive interface example 1 use tkw; -- use the main widget class Tk := tkw(); Tk(OM) := "Example 1"; -- create the Tk interpreter frtop := Tk("frame","200,10"); -- put in two frames as 'supports' frtop("side") := "top"; frtop("background") := "yellow"; fleft := Tk("frame","10,190"); fleft("side") := "left"; fleft("background") := "red"; but := Tk("button","Button1"); but("side") := "left"; -- create a button but("pack,anchor,expand,fill") := "nw,1,both"; -- set some of its packing attributes but := Tk("button","Button2"); but("side") := "left"; -- create a second button but("pack,anchor,fill") := "nw,both"; -- set some of its packing attributes Tk.mainloop(); -- enter the Tk main loop end test;The next example is almost identical with the preceding one, but does not set the 'expand' attribute of the first button, so the available space is filled only vertically, but not horizontally.