11. Some Useful GUI Operations

Before we give the final GUI-based LIFE animation program, we provide a module, winmisc, which contains some useful GUI operations:

IMPLEMENTATION MODULE winmisc;
IMPORT lifemisc, deltaEventIO, deltaIOSystem;
FROM deltaDialog IMPORT Beep, OpenNotice;
FROM deltaPicture IMPORT DrawFunction, Point, Rectangle,
                         SetFont, MovePenTo, DrawString;
FROM deltaFont IMPORT Font, FontName, FontStyle, FontSize, FontInfo,
                      SelectFont, FontMetrics;

The following macro divides a number by two, using shifting:

MACRO Half x -> SHIFTR% x 1;

The Message function of section 8 appears here in a general form. Both user and system states are provided as (strict) arguments, but the function is generic in that it accepts any system state:

RULE
:: Message [STRING] !s !(IOState s) -> (!s, !IOState s);
   Message mess s io 
     -> (s, io'),
        (dummybutton, io'): OpenNotice notice io,
        notice: Notice mess (NoticeButton 1 "OK") [];

We can use this function to define Error similarly to section 8, and Crash which handles fatal errors by quitting after the dialog box is dismissed:

:: Error STRING !s !(IOState s) -> (!s, !IOState s);
   Error str s io
     -> Message ["AN ERROR HAS OCCURRED", "", str] s (Beep io);

:: Crash STRING !s !(IOState s) -> (!s, !IOState s);
   Crash str s io
     -> (s', QuitIO io'),
        (s',io'): Message ["A FATAL ERROR HAS OCCURRED", 
                           "", str] s (Beep io);

The Verify function opens a dialog box containing a message, and applies the given state transition function only if the user selects the Yes (instead of the default Cancel) button:

:: Verify STRING (=> s (=> (IOState s) (s, IOState s)))
                        !s !(IOState s) -> (!s, !IOState s);
   Verify mess f s io
     -> (s, io'), IF = button 1
     -> f s io',
        (button, io'): OpenNotice notice io, 
        notice: Notice [mess] (NoticeButton 1 "Cancel") 
                            [NoticeButton 2 "Yes"];

The ExtendToState function extends a state transition function on the system state to act on a pair of user and system states:

:: ExtendToState !(=> (IOState s) (IOState s))
                        !s !(IOState s) -> (!s, !IOState s);
   ExtendToState f s io -> (s, f io);

The following two functions convert boolean values to the algebraic data types MarkState and SelectState used in GUI specifications:

:: BTOMarkState !BOOL -> MarkState;
   BTOMarkState  b -> Mark, IF b
                   -> NoMark;

:: BTOSelectState !BOOL -> SelectState;
   BTOSelectState  b -> Able, IF b
                     -> Unable;

We also provide a functions for drawing text in a window in any desired font. The following auxiliary function returns a font and its associated metrics, given the name, style (e.g. bold or italic), and size. The default font is returned if the desired font is not available. A legal font and its corresponding metrics are always returned. The calculated metrics are maximum ascent above baseline, maximum descent below baseline, maximum character width, and full height (including leading):

:: GetFont !FontName ![FontStyle] !FontSize  
                        -> (!Font, !INT, !INT, !INT, !INT);
   GetFont fname styles siz
     -> (font, asc, desc, mw, height),
        (ok, font): SelectFont fname styles siz,
        (asc, desc, mw, lead): FontMetrics font,
        height: + asc (+ desc lead);

The ShowText function recursively draws a list of strings, with the baseline of the first line starting at (x,y). Subsequent lines start at (x,y+h), (x,y+2h), etc. Strings which fall vertically outside the given rectangle are not drawn (i.e. we restrict lo <= y <= hi):

:: ShowText ![STRING] !INT !INT !INT !INT !INT 
                                !Rectangle !Picture -> Picture;
   ShowText text x y asc desc h ((a,b),(c,d)) pic 
     -> ShowTextAux text x y h lo hi pic,
        lo: - b desc,
        hi: + d asc;

:: ShowTextAux ![STRING] !INT !INT !INT !INT !INT 
                                !Picture -> Picture;
   ShowTextAux [] x y h lo hi pic -> pic;
   ShowTextAux [s|t] x y h lo hi pic
     -> pic, IF > y hi
     -> ShowTextAux t x (+ y h) h lo hi pic, IF < y lo
     -> ShowTextAux t x (+ y h) h lo hi pic'',
        pic': MovePenTo (x,y) pic,
        pic'': DrawString s pic';

The DrawText function (which is exported) calls ShowText to draw text in a given rectangle, with the top left corner of the first character at the point (x,y). The requested font information is processed using GetFont. This function was used to generate the text in the dialog box of Figure 8.

:: DrawText !Rectangle !FontName ![FontStyle] !FontSize  
                        !Point ![STRING] !Picture -> Picture;
   DrawText rect fname styles siz (x,y) text pic 
     -> ShowText text x (+ y asc) asc desc height rect pic',
        (font, asc, desc, mw, height): GetFont fname styles siz,
        pic': SetFont font pic;

The TextWindow function defines a scrollable window for viewing a given list of strings in a specified font. The function is generic for any user and system state (which must be specified to be unique). The window has a special 'I-Beam' cursor, but does not respond to keyboard or mouse events. An example window created by a call to this function is shown in Figure 5.

:: TextWindow WindowId WindowPos WindowTitle InitialWindowSize 
        FontName [FontStyle] FontSize [STRING] -> WindowDef UNQ s UNQ io;
   TextWindow winid winpos wintitle initsize fname styles siz text
     -> ScrollWindow winid winpos wintitle
            (ScrollBar (Thumb 0) (Scroll hscroll))
            (ScrollBar (Thumb 0) (Scroll vscroll))
            windomain minsize initsize
            (TextUpdate font text x y asc desc h)
            [Cursor IBeamCursor],

The specification defines a domain whose height is just sufficient to hold the given strings, and whose width allows for 100 characters of maximal width (calculating an exact width is also possible, but more time-consuming). Clicking on the scrollbars scrolls in units of 10 characters horizontally, or 3 lines vertically. The first character is positioned so as to allow a blank margin of half a character width horizontally, and half a line vertically. An initial window size is specified as a parameter to the function, but once created the window can be resized up to the domain size, or down to a minimum size (which in this case allows 3 lines of 30 characters to be displayed):

        (font, asc, desc, mw, h): GetFont fname styles siz,
        length: Len text,
        windomain: ((0,0),(width,height)),
        height: * h (++ length),
        width: * mw 101,
        hscroll: * 10 mw,
        vscroll: * 3 h,
        minsize: (* 31 mw, * 4 h),
        x: Half mw,
        y: + asc (Half h);

All windows must have an update function, which takes a list of rectangles (UpdateArea), and a user state, and calculates a list of drawing functions which redraw the required parts of the window. We use Map and ShowText to do this. The user state s must be specified unique here, since the IOState context in most function declarations (which implicitly specifies s as unique) is not present:

:: TextUpdate Font [STRING] !INT !INT !INT !INT !INT 
                        UpdateArea !UNQ s -> (!s, [DrawFunction]);
   TextUpdate font text x y asc desc h areas s 
     -> (s, [SetFont font | Map (ShowText text x y asc desc h) areas]);

The winmisc definition module (winmisc.dcl) exports the required functions, and the types which they use:

UpTop Level

BackAn Animation Program

DCLwinmisc.dcl

ForwardA User-Defined Control