Tutorial

To use FormsVBT, you need a copy of SRC Modula-3 (Version 3.3 or later) and an X server for your system. If you have these, you may want to compile and run the example programs as you read this chapter.

Getting Started

The first example program is in the file Hello.m3:


MODULE Hello EXPORTS Main;
IMPORT FormsVBT, Trestle;
VAR fv := FormsVBT.NewFromFile("Hello.fv"); BEGIN
  Trestle.Install(fv);
  Trestle.AwaitDelete(fv)
END Hello.

The program builds a form (sometimes called a ``dialog box'' or a ``user interface'') whose description is contained in a file named Hello.fv. It installs the form in a top-level window, and then waits until that window is deleted by the user. The window installed by the program is shown in the left half of Fig. [fig:hello] .

The ``Hello FormsVBT!'' example program. The initial version is on the left; the second version on the right.

The file Hello.fv contains the following S-expression:


(VBox  
  (Text "Hello FormsVBT!")
  (Bar)
  (HBox (Text "Left") (Bar) (Text "Right")))

The top-level component is a VBox. A VBox takes an arbitrary number of ``children'' (sub-components) and arranges them vertically from top to bottom. This VBox has 3 children: Text, Bar, and HBox. A Text displays a text string, a Bar draws a line orthogonal to the orientation of its parent, and an HBox arranges its children horizontally, from left to right. The HBox has 3 children, two Texts and one Bar.

The standard way that you compile and link your programs is to use m3build. The m3makefile for the ``Hello FormsVBT!'' application is as follows:


import         (formsvbt)
implementation (Hello)
program        (Hello)

Then you can compile and link the Hello program by typing the shell-command m3build -S in the directory containing the source code.

Actually, most Modula-3 programmers follow the convention of storing all of the source files for an application in a directory called src. The m3build command, when run from src's parent directory, stores all of its derived files (including the executable) in a subdirectory whose name depends on the platform on which you are running. For example, on DECstations, the derived directory is DS; on an Alpha running OSF, the directory is AOSF. When you follow this directory structure, you should invoke m3build without any arguments.

Here's a slightly fancier version of the interface (shown in the right half of Fig. [fig:hello] ):


(Rim (Pen 20)
  (Border (Pen 1)
    (Rim (Pen 2)
      (Border (Pen 2)
        (VTile
          (Text "Hello FormsVBT!")
          (HBox
            (LabelFont (PointSize 240))
            (Color "White")
            (Text (BgColor "Pink") "Left")
            (Bar)
            (Text (BgColor "VividBlue") "Right")))))))

The top-level component is a Rim whose Pen property has a value of 20. A Rim must contain exactly one child (a Border in this case), and it surrounds its child with some background space. Here, the Rim provides 20 points of background space between each edge of the window manager's window frame and the rest of the interface. A Border is just like a Rim, but draws with the foreground color instead of the background color. We replaced the VBox with a VTile, and deleted its Bar child. A VTile is like a VBox, but it also automatically inserts a dividing bar between its children; by dragging the dividing bar, the user can control the division of space among the children. In this example, the HBox has been given two properties, Color and LabelFont. These control the foreground color and font used by the HBox and all of its descendants. Similarly, the BgColor property changes the background color used.

The fancy version of ``Hello FormsVBT!'' is in the file HelloFancy.fv. To run the application using this file, either modify the application to use HelloFancy.fv or rename the file HelloFancy.fv to be Hello.fv. Alternatively, you might find it enjoyable to run the FormsVBT interactive UI builder, formsedit. Just type the shell-command


    formsedit HelloFancy.fv


Exercise 1 Write the FormsVBT S-expression for T^4, a Trestle Tiling Monster of Order 4. (See the Trestle Tutorial, Fig. 2 on page 5.)


Resources

A resource is constant data needed by an application program at runtime; often it is ``loaded'' at startup time. Almost all FormsVBT programs have resources, such as the .fv (pronounced ``dot ef vee'') files that specify the user interface. Other typical resources specific to an application include bitmaps, cursors, and help-texts.

When an application is built, its resources can be ``bundled'' with the executable image. The primary benefit of this feature is that applications are self-contained with respect to the resources they need. Thus, you can copy an executable to a remote site and you won't need to copy the resource files and install them in the same place as they were when the application was built. Also, your application will be insulated against changes in library resources.

The easiest way to do this is to name the resources and the bundle in the m3makefile, as in this example:


import         (formsvbt)
resource       (Hello.fv)
bundle         (HelloBundle)
implementation (Hello)
program        (Hello)

The second line declares that there is a resource named Hello.fv. The third line has the effect of collecting all the named resources (only one in this case) and creating an interface called HelloBundle that provides access to them. The program would then be modified to look like this:


MODULE Hello EXPORTS Main;
IMPORT FormsVBT, HelloBundle, Rsrc, Trestle;
VAR 
  path := Rsrc.BuildPath(HelloBundle.Get());
  fv   := NEW (FormsVBT.T).initFromRsrc ("Hello.fv", path);
BEGIN
  Trestle.Install(fv);
  Trestle.AwaitDelete(fv)
END Hello.

The call to HelloBundle.Get returns a bundle that is used to create a resource-path, which is then searched by the initFromRsrc method.

But what if you want the application to use new resource files? For example, you might have changed some details of the .fv file that don't require any changes to the application code. Do you have to rebuild the entire application?

Fortunately, the answer is no. However, you do need to tell FormsVBT that you want it to look for those resources in the file system before it looks for them among the resources that were bundled into the application. You do this by changing the resource-path so that it includes one or more directories before the bundle.

The convention is to use environment variables whose names are spelled by combining the program's name with the string "PATH". This variable should be set to a list of directory-names, each separated by a colon. So, if you want to run the Hello program using the Hello.fv file that's in Smith's home directory instead of the one that's bundled with the application, you would type something like this shell command:


    setenv HelloPATH /user/smith

In the program, you would construct a resource-path that included this directory by adding the name HelloPATH, prefixed with a dollar sign:


MODULE Hello EXPORTS Main;
IMPORT FormsVBT, HelloBundle, Rsrc, Trestle;
VAR 
  path := Rsrc.BuildPath("$HelloPATH", HelloBundle.Get());
  fv   := NEW (FormsVBT.T).initFromRsrc ("Hello.fv", path);
BEGIN
  Trestle.Install(fv);
  Trestle.AwaitDelete(fv)
END Hello.

The FormsVBT Language

Syntactically, there are three types of components in FormsVBT: leaves, filters, and splits. A leaf has no children; a filter has exactly one child; and a split has any number of children.

The FormsVBT leaf components include passive objects like texts and pixmaps, as well as interactive objects like scrollbars and type-in fields.

A filter modifies its child's looks or behavior in some way. We've seen how a Border draws a border around its child. Another common filter is Boolean. It adds a check box to the left of its child and makes the box and the child sensitive to mouse clicks. It's important to realize that the child may be any arbitrarily complex arrangement of components, although a Text component is the most common.

The purpose of most splits is to divide the display area among its component-children (sub-components). In addition to the horizontal and vertical splits that we've seen, FormsVBT provides a temporal split (TSplit) to display exactly one child at any given time, and a z-axis split (ZSplit) to display children as overlapping subwindows.

Components are written as lists containing the component's type, followed by some number of properties, followed by some number of sub-components. Properties are written as lists containing a keyword and a value. For example, in the S-expression:


  (HBox
    (LabelFont (PointSize 240)) 
    (Color "White")
    (Text "Left") 
    (Bar) 
    (Text "Right")

the parent-component's type is HBox. This component has two properties; the first property has the keyword LabelFont and the value (PointSize 240); the second has the keyword Color and the value "White". It has three sub-components: (Text "Left"), (Bar), and (Text "Right").

The value of each property is type-checked when the description is parsed. The possible types include strings, integers, and real numbers, as well as more complicated types like color and font specifications.

So far, we have seen two kinds of properties. Class properties, like Pen, are defined in conjunction with specific components, and are allowed only on components of that class. Inherited properties, like Color and LabelFont, may be specified for any component, though they are not relevant to all component types. The inherited properties have the feature that a value specified for one component becomes the default value for all descendants of that component. Thus an inherited property applies not to one component, but to an entire subtree.

FormsVBT supports a third type of property, universal properties. A universal property can be specified on any component, and its value applies only to that component. <--! %Thus, universal %properties are like class properties in the sense that their values %apply to the particular component on which the property is specified. %Universal properties are also like inherited properties in the sense %that they can be specified for all components. However, the value of a %universal property does not propagate; it applies only to the %component on which it is specified. % To summarize, the FormsVBT language offers a rich set of composition mechanisms % and a variety of predefined objects, allowing easy specification and % implementation of complex user interfaces. This philosophy is quite similar to % that of InterViews~\cite{interviews}, a popular C++ toolkit running on X. % Nearly all of the primitive objects found in the VBTkit and Trestle window % system, upon which FormsVBT is based, have counterparts in InterViews. -->


Exercise 2: In HelloFancy.fv, wrap a Scale component around the top-level Rim. The Scale has two class properties: HScale and VScale. What happens when the values of both of these properties are set to 1.75? What happens when you nest Scale filters?


The Three-Cell Calculator Application

A more interesting application is a three-cell calculator. Readers may wish to compare the FormsVBT implementation of this example with that of SUIT [suit] . The user can enter two numbers and an arithmetic operation to perform on the two numbers. The result is computed and displayed whenever the user selects a new arithmetic operation or types a new number. Fig. [fig:calc3cell] shows the application in action.

The user interface is described by the following FormsVBT expression:


(Shape (Width 300 + 100 - 50) (Height + 25)
  (Rim (Pen 20)
    (VBox
      (HBox
        (VBox Fill (Numeric %num1 =5) Fill)
        (Radio %functions =add
          (VBox
            (Choice %div "divide")
            (Choice %mul "multiply")
            (Choice %sub "subtract")
            (Choice %add "add")))
        (VBox Fill (Numeric %num2 =2) Fill)
        (Text "=")
        (Text %result LeftAlign ""))
      (Glue 10)
      (HBox Fill (Guard (Button %exit "QUIT")) Fill))))

The tokens that start with percent signs are names assigned to components. For example, the Text component where the application stores the result of each computation is named result. An application can access only named components at runtime.

The three-cell calculator application.

This form contains the following components that we have not seen before:

In FormsVBT, as in most GUI toolkits, an application is structured as an initialization routine, which runs in one thread, and a collection of event-handling procedures, which run in other threads. When an application is run, it initializes dialogs and then transfers control to the toolkit. The main thread waits until the toolkit returns control, which it does when all the dialogs have been deleted.

Here is the complete application for the three-cell calculator (see the file Calc3Cell.m3):


MODULE Calc3Cell EXPORTS Main;
IMPORT Fmt, FormsVBT, Text, Trestle, VBT;

PROCEDURE NewForm (): FormsVBT.T =
  VAR
    fv  := FormsVBT.NewFromFile ("Calc3Cell.fv");
    qcl := NEW (FormsVBT.Closure, apply := Quit);
    ccl := NEW (FormsVBT.Closure, apply := Compute);
  BEGIN
    FormsVBT.Attach (fv, "exit", qcl);
    FormsVBT.Attach (fv, "num1", ccl);
    FormsVBT.Attach (fv, "num2", ccl);
    FormsVBT.Attach (fv, "functions", ccl);
    RETURN fv
  END NewForm;

PROCEDURE Quit (cl  : FormsVBT.Closure;
                fv  : FormsVBT.T;
                name: TEXT;
                time: VBT.TimeStamp) =
  BEGIN
    Trestle.Delete (fv)
  END Quit;

PROCEDURE Compute (cl  : FormsVBT.Closure;
                   fv  : FormsVBT.T;
                   name: TEXT;
                   time: VBT.TimeStamp) =
  VAR
    answer: REAL;
    first        := FLOAT (FormsVBT.GetInteger (fv, "num1"));
    second       := FLOAT (FormsVBT.GetInteger (fv, "num2"));
    fn           := FormsVBT.GetChoice (fv, "functions");
  BEGIN
    IF Text.Equal (fn, "add") THEN
      answer := first + second
    ELSIF Text.Equal (fn, "sub") THEN
      answer := first - second
    ELSIF Text.Equal (fn, "mul") THEN
      answer := first * second
    ELSIF Text.Equal (fn, "div") THEN
      answer := first / second
    END;
    FormsVBT.PutText (fv, "result", Fmt.Real (answer))
  END Compute;

BEGIN 
  VAR fv := NewForm(); BEGIN
    Trestle.Install(fv);
    Trestle.AwaitDelete(fv)
  END
END Calc3Cell.

The parameters to an event-handler (e.g., Quit and Compute in the Calc3Cell program) identify the dialog (fv) in which the event happened and the name of the interactor causing the event.

The event-handler's first parameter, named cl in this example, is a FormsVBT.Closure that is specified when the event-handler is attached. Its apply method is the event-handler. The standard way of passing additional information to the event-handler is to create a subtype of FormsVBT.Closure, with new fields, and possibly new methods, for handling the new information. The time parameter is a timestamp associated with the user event that caused the event-handler to be invoked. The timestamp is needed for certain operations, like acquiring the keyboard focus.

We say that a component ``generates an event'' when the user does something in a component that causes the event-handler to be invoked. The semantics of what causes an event to be generated is specific to each component.

The Three-Cell Calculator application creates a form and passes it to Trestle, the window manager, which ``installs'' it, just as the ``Hello FormsVBT!'' application did. Here, as part of building a form from the S-expression in file Calc3Cell.fv, we also attach event-handlers to the components to which the application will respond. The Quit event-handler, which is attached to the component named exit (the button labeled ``QUIT''), deletes the window from Trestle. The Compute event-handler, which is attached to both of the Numeric components as well as the radio buttons, retrieves the values stored in both Numeric components, determines which arithmetic function the user selected, performs the operation, and then displays the result.


Exercise 3 Add your favorite operator to the application and to the user interface. (If you're undecided about which operator is your favorite, try GCD.)


Improving Readability

The Three-cell Calculator S-expression illustrates a number of common abbreviations that help make the FormsVBT language more readable.

A percent sign is an abbreviation for the Name property. That is, the FormsVBT parser reads %xyz exactly as if it were (Name xyz).

An equals sign is an abbreviation for the property called Value. That is, the FormsVBT parser reads = xyz exactly as if it were (Value xyz). By convention, any component whose value can be changed interactively by a user has a Value property.

Components that display some type of object, like a string or a pixmap, specify the object using a property called Main. For example, to display a pixmap from a file named Trumpet, you'd say (Pixmap (Main "Trumpet")). However, the Main property can be abbreviated by omitting the keyword Main and the associated parentheses, e.g., (Pixmap "Trumpet").

A Text component that has no properties other than Main can be further abbreviated simply by giving a string. For example, (Text (Main "QUIT")) can be reduced to (Text "QUIT") and then to "QUIT". Other examples of this are the children of the four Choice components in the last program. If you want to specify any properties on a Text component (such as a name, font, color, or alignment), you can abbreviate Main, but you still need to write (Text ...).

Boolean properties have a value of either TRUE or FALSE. The default value of all Boolean properties is FALSE. Mentioning the name of a Boolean property is an abbreviation for specifying a true value. For example, in the Three-Cell Calculator, the token LeftAlign is an abbreviation for (LeftAlign TRUE).

Finally, leaf components without any properties can be written without parentheses, e.g., Fill.

The following chart summarizes these abbreviations:
(Text "t") "t"
(Name n) %n
(Value v) =v
(Main m) m
(boolprop TRUE) boolprop
(proplessleaf) proplessleaf


Exercise 4 The following interface contains a textual label, a type-in field, and a button:

The interface is 250x75 points, and it uses Button, Frame, Pixmap, Rim, Shape, Text, and TypeIn components, in addition to some HBoxes, VBoxes, Glues, and Fills. Appendix [ap:longcatalog] describes the class-specific properties for each component. Write a concise FormsVBT expression for this form.


FormsVBT provides two additional ways to make S-expressions more readable. First, an S-expression can be split across multiple files (resources). To insert a file named HelpDialog.fv, just include the expression


    (Insert "HelpDialog.fv")

wherever you want the file to be inserted. The Insert expression can appear anywhere in an S-expression; logically, it is replaced by the contents of the named file before the S-expression is parsed.

The second way to make the form more readable is by using macros. Syntactically, a macro is an inherited property with the name Macro. For details on macros, see Section [sec:language-macros] .

Separating the UI from the Application

One of the ways that user interface toolkits like FormsVBT simplify the construction of interactive, graphical applications is by forcing a separation of the interaction-specific parts from the application-specific parts. This allows the interface designer to concentrate on the design of the interface and the application programmer on the implementation of the application-specific code.

In FormsVBT, the only UI components known to the application are those that are given names. The application is insensitive to the layout of components and to the existence of all unnamed components. There is even some insulation between the application and the UI for named components: one component may be replaced with another whose behavior with respect to the application is the same.

For instance, in the Three-Cell Calculator interface from Section [sec:calcIntro] , we could replace the Text-component named result with any other component that can store text, such as a Typescript. A Typescript would capture a history of all values computed by the application. (We would also need to delete the LeftAlign and Main properties to make this change.)

We could also replace the radio buttons with items in a pulldown menu by replacing this expression


  (Radio %functions
    (VBox
      (Choice %div "divide")
      (Choice %mul "multiply")
      (Choice %sub "subtract")
      (Choice %add "add")))

with the following expression:


  (Menu "?" (Radio %functions =add
    (HBox
      (VBox 
        (Choice MenuStyle %div "divide")
        (Choice MenuStyle %mul "multiply"))
      (VBox 
        (Choice MenuStyle %sub "subtract"))
        (Choice MenuStyle %add "add")))))

See Fig. [fig:calc3cell-menu] .

A modified UI for the three-cell calculator application. The cursor (not visible in the figure) is over the string ``multiply.''

The first child of a Menu is the ``anchor'' of a pulldown menu; click on it to get a menu displayed. The second child of a Menu is an arbitrary S-expression, displayed when the user clicks on the anchor. In the example above, the first child is a Text component displaying a question mark. The second child contains four radio buttons, arranged in a 2-by-2 matrix. The MenuStyle property causes a radio button to react when the mouse rolls into it rather than on a mouse click.

The contents of this menu emphasizes an earlier point about composition. A Menu does not impose any structure on the contents of the menu. One merely composes a Menu out of 2 children: a child that is the anchor button, and a child that appears when the anchor button is activated. A ``traditional'' pulldown menu is a VBox whose children are MButtons.


Exercise 5 Change the program so that a symbol for the current operator is displayed instead of the question mark. Hint: Assign name to the quoted question mark, by using the expanded format (Text %op "?"), and call FormsVBT.PutText to change what is displayed in a Text component.


Subwindows

The three-cell calculator will crash if we try to divide by 0. Let's change the application to pop up a dialog box warning the user if there is an attempt to divide by 0. We need to modify the Compute event-handler by adding a test for a divisor equal to zero just before the division:


    ...
    ELSIF Text.Equal (fn, "div") THEN
      IF second = 0.0 THEN
        FormsVBT.PopUp (fv, "errorWindow");
        RETURN
      END;
      answer := first / second
    END;
    ...

The call to FormsVBT.PopUp will cause the named dialog to appear.

It is easy to add a dialog named errorWindow to the calculator's S-expression that was given in Section [sec:calcIntro] . The S-expression becomes the following:


    (ZSplit 
      (ZBackground (Shape ...))
      (ZChassis %errorWindow 
        (Title "Error Message")
        (Rim (Pen 20) 
          (Text %errorText "Can't divide by zero."))))

A ZSplit takes an arbitrary number of children and displays them as overlapping windows. The first child is the background; it is always visible. The visibility and location of the other children are under program control. The ZChassis wraps a ``banner'' around its child; the banner is responsive to mouse activity for the common window controls of closing, moving, and resizing. A call to FormsVBT.PopUp will cause a specified child of a ZSplit to appear. By default, a ZChassis is not initially visible.

Another common use of subwindows is to allow a user to specify additional information for a command. For example, the ``Save As...'' button found in many applications pops up a dialog box, which is a subwindow, with a way to enter the name of a file. A button like ``About Bazinga...'' pops up a subwindow containing information about the application called Bazinga.

In situations like these, it's a burden on the programmer to write an event-handler that simply calls FormsVBT.PopUp. To simplify this common case, FormsVBT provides a PopButton. This component is just like a Button, but before its event-handler is called, it causes a designated subwindow to appear. In practice, applications often don't need to attach any event-handler to a PopButton.

For grins, we'll now change the original three-cell calculator user interface so that the radio buttons are in a subwindow that is completely controlled by the user. Clicking on the "?" menu will cause the subwindow to appear. The window can be closed and repositioned without any application code. We need make two small changes to the original S-expression given in Section [sec:calcIntro] to add subwindows. First, replace the radio buttons by a button that causes a subwindow to pop-up. That is, change


  (Radio %functions ...)

to


  (PopButton (For fnWindow) "?")

Second, move the Radio expression into a subwindow, by enclosing it in a ZChassis, and wrapping a ZSpit around the root. Now, Calc3Cell.fv looks like this:


  (ZSplit
    (ZBackground (Shape ...))
    (ZChassis %errorWindow ...)
    (ZChassis %fnWindow 
      (Radio %functions ...)))


Exercise 6 Add an ``About Three-Cell Calculator...'' button. It should pop-up a subwindow with appropriate information. If you want to put the button inside of a pull-down menu, use PopMButton.


Modal Dialogs

When a subwindow appears, the rest of the form and all other subwindows remain active. In the case of the operator-subwindow in Section [sec:subwindows] (i.e., the ZChassis named fnWindow), this behavior was desirable. However, this behavior may not be desirable for the error-message subwindow. That is, some application writers would like to force the user to explicitly close the error message subwindow before continuing to interact in the application. In the UI jargon, this is called a modal dialog.

A simple way to do this is to bring up the error subwindow as before, but also to ``deactivate'' the background---make it unresponsive to user actions---while the subwindow is displayed. When the dialog is finished, we ``re-activate'' the background. A FormsVBT component called Filter is used to set the reactivity of its child to be active (the default case), passive (mouse and keyboard events are not sent), dormant (like passive, but it also grays out the child and changes the cursor), or vanished (like passive, but also draws over the child in the background color, thereby making it invisible).

Changing the modeless subwindow in the calculator so that it is modal requires only a trivial change. First, add a Filter just inside the ZBackground. Name this component zbg. Second, in the application, add


    FormsVBT.MakePassive(form, "zbg")

after the call to FormsVBT.PopUp. Finally, you need to register an event-handler for the ZChassis named errorWindow. The event handler will be invoked when the subwindow is closed; it contains the following line:


    FormsVBT.MakeActive(form, "zbg")

You might also wish to eliminate the banner on the subwindow. To do so, change the ZChassis to be a ZChild, and add a CloseButton somewhere in the subwindow. The CloseButton button will cause the subwindow in which it is contained to be taken down. Fig. [fig:modal] shows the modified application.

The three-cell calculator application with a modal dialog.


Exercise 7 Make the error window in the three-cell calculator modal in the manner suggested in this section: In the .fv file, add a Filter inside the ZBackground, change the error subwindow from a ZChassis to a ZChild, and add a CloseButton to the error window. In the .m3 file, change the application code so that the background is made passive when the error window appears, and re-activated after error window disappears.



Exercise 8 When the error dialog appears while the subwindow containing operators is visible, the operators are not deactivated, although the main form is deactivated. Change the form so that everything except the error subwindow is made passive. Don't modify the application! (Hint: Use two ZSplits, one the background child of the other.)


A File Viewer

It's easy to hook up the FormsVBT text-editing widget to an application to make a bona fide text editor. The file-viewer application, shown in Fig. [fig:viewer] , contains a type-in area on the top for entering the name of a file and a fully functional text editor that occupies the bulk of the window. The text editor is in read-only mode.

The S-expression for the application, in the file Viewer.fv, is quite simple:


(Rim (Pen 10) (Font (WeightName "Bold"))
  (VBox
    (HBox 
      (Frame Lowered (TypeIn %fileName))
      (Glue 10) 
      (Button %exit "QUIT"))
    (Glue 10)
    (Shape (Height 200 + inf) (Width 300 + inf)
      (Frame Lowered (TextEdit ReadOnly %editor)))))

The application is structured as in the three-cell calculator application in Section [sec:calcIntro] . A NewForm procedure converts the S-expression into a runtime object and registers event-handlers. Only one event-handler is needed here; ReadFile is attached to the type-in field fileName. It is invoked whenever you type a carriage return in the type-in field. The code is straightforward:


PROCEDURE ReadFile (cl  : FormsVBT.Closure;
                    fv  : FormsVBT.T;
                    name: TEXT;
                    time: VBT.TimeStamp) =
  VAR fname := FormsVBT.GetText (fv, "fileName");
  BEGIN
    TRY
      FormsVBT.PutText (fv, "editor", GetFile (fname));
    EXCEPT
      Rd.Failure =>
        FormsVBT.PutText (fv, "editor", "");
        FormsVBT.PutText (fv, "fileName", "");
    END;
  END ReadFile;

The event-handler first retrieves the string you typed into the type-in field named fileName. It then calls an internal procedure GetFile to retrieve the contents of a file by that name, and finally stores the contents into the text-editor widget. If an error is encountered while trying to retrieve the contents of the file, ReadFile catches the exception that is raised and just erases the contents of the type-in field and the text editor. The application is shown in Fig. [fig:viewer] .

A simple file viewer application.

The file viewer application again, but now, file names can be specified in the type-in field at the top or using the file browser at the left.


Exercise 9 Add a Reset button to the left of the Quit button. Clicking on this button should clear the contents of the type-in field. For extra credit, interpret a double click to also clear the contents of the editor. To detect a double-click, you will need to examine the VBT.MouseRec that is available from FormsVBT.GetTheEvent to the Reset button's event-handler.



Exercise 10 Add a pop-up to signal when the file could not be opened, rather than clearing the type-in field.


If you substitute FileBrowser for TypeIn, you'll be able to traverse the file system by double-clicking on directories (those items ending with a slash) in a browser. The file browser generates an event when you double-click on a file. Note that the application does not need be changed at all!

While it's nice to be able to traverse the hierarchy by mousing around in the file browser, there are times when it is more desirable simply to type in a pathname. No problem. We'll just add a type-in field to the S-expression. Here's the new S-expression (see Fig. [fig:viewer2] ):


(Rim (Pen 10) (ShadowSize -1)
     (BgColor "White") (LightShadow "Black")
     (DarkShadow "Black")
  (VBox
    (HBox 
      (Frame Lowered (TypeIn %fileNameString))
      (Glue 10) 
      (Button %exit "QUIT"))
    (Glue 10)
    (HBox
      (Shape (Width 100)
        (Frame Lowered (FileBrowser %fileName)))
      (Glue 10)
      (Shape (Height 200 + inf) (Width 300 + inf)
        (Frame Lowered (TextEdit ReadOnly %editor))))))

(A negative value for the inherited property ShadowSize is a convention for telling FormsVBT to give feedback using a flat, 2-d style rather than a Motif-like, 3-d style.)

We also need to change the application slightly to register the ReadFile event-handler with the type-in field (i.e., fileNameString) as well as with the file browser (i.e., fileName). In addition, procedure ReadFile itself needs to be changed trivially to initialize fname from the interactor that caused the event-handler to be invoked:


    VAR fname := FormsVBT.GetText (fv, name);

So far so good, but there's no tie between the file browser and the type-in field.


Exercise 11 Implement event-handler methods for the file browser (fileName) and the type-in field (fileNameString) to keep them synchronized. That is, typing a path into the type-in field should cause the browser to change the directory it is displaying. The directory displayed by the file browser is set by calling FormsVBT.PutText, passing in the name of the directory to be displayed. What happens if you specify a directory that doesn't exist?


If you didn't do the last exercise, now is your last chance...

It turns out that FormsVBT already provides a component that ties a type-in field to a file browser. The component is called a Helper, and it has a class-specific property called For that names the file browser to which it is tied. So, if you change the expression


    (TypeIn %fileNameString)

to


    (Helper (For fileName))

and replace the initialization of variable fname in the original program as described above, then the type-in field and the file browser will stay synchronized, without any application-code intervention.


Exercise 12 What happens when you replace ``Helper'' by ``DirMenu''? What happens when you tie a file browser to both a Helper and DirMenu?