SML PROGRAMMING

Fun with Widgets

While the "SML Developer's Toolkit" contains a number of simple examples to display text, get YES/NO responses, pick coverages, and so on, many situations may call for dialog boxes with more complex functionality. It is possible to design a variety of complex widgets, including drop-lists, check boxes, radio buttons, and embedded list boxes; in general, implementation involves building the dialog box on the fly, displaying it using POPUP's NONE option, and updating it as appropriate.

Drop-Lists

For example, in ARCEDIT you may want one dialog box to set all of the annotation edit parameters: instead of creating multiple buttons for each enumerated item value such as ANNOPOSITION, you may wish to implement drop-lists. In appearance, a drop-list is a box with a down-arrow button next to it; the box contains a member of a list of allowed values, which in turn may be invoked by hitting the down-arrow. As shown in the example below, the list which is invoked is a simple popup containing one button for each value. Let's look first at the routine which generates the main dialog box[1]:

Figure 1

Annotation parameters are contained in variables 41-46. Note the use of FORTRAN formatting to preserve constant box width. Also note the use of "%%" so that the variable references for the edit boxes are written literally to the menu file. When invoked with ARCEDIT defaults, the menu looks like this (converted to grayscale):

Figure 2

The dialog box contains five buttons and three edit boxes[2]; the values within the drop-list boxes are disabled by preceding them with acute grave ("`") characters. To activate a drop-list the user picks one of the down-arrow buttons, which return either "V1", "V2", or "V3" (the numeric identifiers are rendered invisible by a color escape sequence with identical foreground and background colors). With all that in mind, let's take a look at the parent routine:

&routine setanno

SHOW ANNOTYPE 41
SHOW ANNOPOSITION 42
SHOW ANNOFIT 43
SHOW ANNOLEVEL 44
SHOW ANNOSIZE 45
SHOW ANNOITEM 46
&run annogen
&sv 11
SCREENSAVE t$anno.ras POPUP 8 10 16 29
&while &ne %11 EXIT &do
   POPUP t$anno.mnu 11 2 8 10 16 29 NONE %11
   &if &eq %11 V1 &do
      &openw t$anno2.mnu
      &write "Ē06 POINT1"
      &write " POINT2"
      &write " LINE"
      &closew
      POPUP t$anno2.mnu 12 1 14 11 3 7 RESET %41
      & DEL t$anno2.mnu
      &if &ne %12 EXIT &do
         &sv 41 %12
         &run annogen
      &end
   &elseif &eq %11 V2 &do
      &openw t$anno2.mnu
      &write "Ē06 LL"
      &write " LC"
      &write " LR"
      &write " CL"
      &write " CC"
      &write " CR"
      &write " UL"
      &write " UC"
      &write " UR"
      &closew
      POPUP t$anno2.mnu 12 1 14 23 9 4 RESET %42
      & DEL t$anno2.mnu
      &if &ne %12 EXIT &do
         &sv 42 %12
         &run annogen
      &end
   &elseif &eq %11 V3 &do
      &openw t$anno2.mnu
      &write "Ē06 ON"
      &write " OFF"
      &closew
      POPUP t$anno2.mnu 12 1 14 31 2 4 RESET %43
      & DEL t$anno2.mnu
      &if &ne %12 EXIT &do
         &sv 43 %12
         &run annogen
      &end
   &elseif &eq %11 OK &do
      ANNOTYPE %41
      ANNOPOSITION %42
      ANNOFIT %43
      ANNOLEVEL %44
      ANNOSIZE %45
      ANNOITEM %46
      &sv 11 EXIT
   &else
      &sv 11 EXIT
   &end
&end
SCREENRESTORE t$anno.ras
& DEL t$anno.mnu
& DEL t$anno.ras
&return
In the above text I substitute "Ē" for ASCII 274[3]. When the response is "V1", a POPUP dialog box containing the three ANNOTYPE options is invoked directly below the appropriate box:

Figure 3

The cursor position is set to the current value. At this point the user can click on a value or escape by pressing the <`> key. If a value is chosen, variable 41 is updated and the main dialog file is regenerated. Once the "OK" button is pressed, the annotation edit parameters are updated with the contents of variables 41-46.

Check Boxes

There are times when it would be desirable to pick multiple items in a list. For example, one may wish to choose specific items to apply to ARCEDIT's FORMS or LIST command. Such may be facilitated through the use of check boxes, which in the example below are simply text widgets (disabled by "`") that are toggled by hitting the button representing a particular entry (the assumption being that each entry is a unique value, not equal to "OK", "CANCEL", or "EXIT"):

&routine getitem

&include getitem.inc
&openw t$temp.lis
ITEMS LIST
&closew
&open t$temp.lis error
&sv [numitems] 0
&sv [c_index] 100
&sv [i_index] 200
&while &do
   &read [temp] [break]
   &inc [numitems]
   &inc [c_index]
   &inc [i_index]
   &delim * *
   &sv %*c_index* "[`]"
   &delim [ ]
   &sv %[i_index] [temp]
&end
&close
& DEL t$temp.lis
&cv [rows] [numitems] + 7
&if &nr [rows] 1 20 &do
   &sv [rows] 20
&end
SCREENSAVE t$item.ras POPUP 8 10 [rows] 17
&sv [cursor]
&sv [dialog]
&while &ne [dialog] EXIT &do
   &run genitm
   POPUP t$item.mnu [dialog] 2 8 10 [rows] 17 NONE [cursor]
   &sv [cursor] [dialog]
   &if &eq [dialog] OK &do
      &sv [resp]
      &sv [i] 1
      &while &rn [i] 1 [numitems] &do
         &cv [c_index] 100 + [i]
         &cv [i_index] 200 + [i]
         &value [temp] %[c_index]
         &delim * *
         &if &eq "*temp*" "[X]" &do
            &value *temp* %*i_index*
            &sv *resp* "*resp* *temp*"
         &end
         &delim [ ]
         &inc [i]
      &end
      &sv [dialog] EXIT
   &elseif &eq [dialog] CANCEL &do
      &sv [resp] CANCEL
      &sv [dialog] EXIT
   &elseif &eq [dialog] EXIT &do
      &sv [resp] CANCEL
   &else
      &sv [i] 1
      &while &rn [i] 1 [numitems] &do
         &cv [c_index] 100 + [i]
         &cv [i_index] 200 + [i]
         &value [temp] %[i_index]
         &if &eq [temp] [dialog] &do
            &value [temp] %[c_index]
            &delim * *
            &if &eq "*temp*" "[`]" &do
               &sv %*c_index* "[X]"
            &else
               &sv %*c_index* "[`]"
            &end
            &delim [ ]
         &end
         &inc [i]
      &end
   &end
&end
SCREENRESTORE t$item.ras
& DEL t$item.ras
& DEL t$item.mnu
&return
&label error
&type "Error opening t$temp.lis"
&return
The include file "getitem.inc", which merely contains named variable assignments, is left out for brevity. In the first part of the routine, the output of the ITEMS LIST command is written to a temporary file; the file is then read and each entry loaded into an SML variable starting at 201. A corresponding empty check box "[`]" is assigned starting at variable 101 (note the use of &delim to prevent COMPSML from bombing). Then the appropriate number of rows for the dialog box is determined (maximum 20) and we're ready to crank it out. Routine "genitem" is as follows:

Figure 4

When the user hits one of the entry buttons, the parent routine sets the corresponding checkbox variable to "[X]" or "[`]" as appropriate. Depending on the coverage, the dialog box looks something like this:

Figure 5

If the number of items causes the total dialog box rows to exceed 20, a "PgDn" button will appear to scroll to the remainder of the box[4].

Figure 6

When the user hits the "OK" button, the routine looks through the checkbox variables, and upon finding one that's checked appends the corresponding entry to the response variable [resp]. Note that in this example the total length of the picked items plus spaces cannot exceed 80 characters or the response will be cropped.

Radio Buttons

Radio buttons are identical to checkboxes, save that only one may be checked for each group of entries. For the most part, radio buttons are unnecessary as you could simply create a separate popup for each group of entries, but if you're like me you may want something a little more spiffy. The following example sets ARCEDIT's COORDINATE CURSOR or DIGITIZER options:

Figure 7

Note that when "CURSOR" is checked, the "DIGITIZER" options are disabled and grayed out:

Figure 8

The options are enabled when "DIGITIZER" is checked:

Figure 9

The parent routine simply sets the appropriate radio button variables to "(*)" or "(`)" based on the current SHOW COORDINATE value and resets them appropriately for each option that is checked:

&routine coo

SCREENSAVE t$coo.ras POPUP 8 10 15 17
SHOW COORDINATE 1
&sv 10
&while &ne %10 EXIT &do
   &if &eq %1 CURSOR &do
      &sv 41 "(*)"
      &sv 42 "(`)"
      &sv 43 "(`)"
      &sv 44 "(`)"
      &sv 45 "(`)"
   &elseif &eq %1 DIGITIZER &do
      &sv 41 "(`)"
      &sv 42 "(*)"
      &sv 43 "(*)"
      &sv 44 "(`)"
      &sv 45 "(`)"
   &elseif &eq %1 NONE &do
      &sv 43 "(*)"
      &sv 44 "(`)"
      &sv 45 "(`)"
   &elseif &eq %1 DEFAULT &do
      &sv 43 "(`)"
      &sv 44 "(*)"
      &sv 45 "(`)"
   &elseif &eq %1 COVERAGE &do
      &sv 43 "(`)"
      &sv 44 "(`)"
      &sv 45 "(*)"
   &else
      &sv 10 EXIT
   &end
   &if &ne %10 EXIT &do
      &run gencoo
      POPUP t$coo.mnu 1 2 8 10 15 17 NONE %1
   &end
&end
SCREENRESTORE t$coo.ras
& DEL t$coo.ras
& DEL t$coo.mnu

&rem Process response...

&return
Now wouldn't it be neat, should the user select the "DIGITIZER COVERAGE" option, to bring up a coverage picker? Sure it would! Which brings us to:

List Boxes

@PICK is a coverage picker that uses the POPUP command to generate a dialog box containing scrolling list boxes:

Figure 10

Basically, an embedded, scrollable list box is a set of buttons representing a subset of a list of values stored in SML variables; arrow buttons are used to page up or down through that list. In this example it only picks existing coverages, but could be expanded to ask the user to input a nonexistent coverage name, e.g. for ARCEDIT's CREATECOVERAGE command.

@PICK is intended to be incorporated into a larger application, e.g. an ARCEDIT or ARCPLOT shell, with DISP 4 active. The usage is as follows:

   &run pick [target]
where [target] is the desired variable to contain the response, which will either be the name of a coverage, including its full path, or "CANCEL". @PICK consists of four routines ("pick", "genplis", "genpdlg", and "sysdir") and an include file ("pick.inc"). The include file merely defines the named variables used in the routines:

&define i 9 &var
&define resp 10 &var
&define cover 11 &var
&define curdir 12 &var
&define home 13 &var
&define wksp 14 &var
&define exit 15 &var
&define i_cov 16 &var
&define i_dir 17 &var
&define numcov 18 &var
&define numdir 19 &var
&define temp1 20 &var
&define temp2 21 &var
&define entry1 22 &var
&define entry2 23 &var
&define var 24 &var
The numeric assignments are arbitrary, and may be reassigned to accommodate other routines, as long as they don't collide with the edit box variables (41 and 42 in this example, but they in turn may be reassigned to different variables); the only reserved variable is 1, the argument variable.

One of the first steps in generating list boxes is to load in list values: routine "genplis" reads in coverage values and transfers them to SML variables beginning at 101, while directories begin at variable 201:

&routine genplis

&include pick.inc
&type "Retrieving directory
information..."
&sv [cover]
&sv [i_cov] 1
&sv [numcov] 0
&sv [i_dir] 1
&sv [numdir] 0
& LQ C | SORT >[wksp]t$cov.lis
& LQ D | SORT >[wksp]t$dir.lis
&sv [i] 100
&open [wksp]t$cov.lis error
&while &do
   &read [temp1] [break]
   &inc [i]
   &sv %[i] [temp1]
   &inc [numcov]
&end
&close
&sv [i] 200
&open [wksp]t$dir.lis error
&while &do
   &read [temp1] [break]
   &inc [i]
   &sv %[i] [temp1]
   &inc [numdir]
&end
&close
&return
&label error
&type "GENPLIS: I/O error"
&return
Note the two lines which execute LQ, a 3.4.2 utility. Version 3.4D+ users could substitute the following code:

& L -CHLC >[wksp]t$cov.lis
&length [temp1] [curdir]
&value [temp2] [curdir] [temp1] [temp1]
&if &ne [temp2] \ &do
   &openw [wksp]t$dir.lis
   &write ".."
   &closew
   & L -CHLD >>[wksp]t$dir.lis
&else
   & L -CHLD >[wksp]t$dir.lis
&end
The disadvantage of using L is that the directory list includes coverages, and drives are not listed[5]. Routine "genpdlg" creates the actual dialog box:

Figure 11

In any given instance, the dialog box will contain two edit boxes and six buttons (plus any list box entries). Both list boxes are set to six rows. Index variables [i_cov] and [i_dir] show the routine where to start generating the entries for the respective list boxes, and variables [numcov] and [numdir] indicate whether the end of a list is passed, in which case the rest of the list box is padded with blank entries.

The parent routine is as follows:

&routine pick

&include pick.inc
&value [var] 1
&run sysdir [curdir]
&value [home] [curdir]
&value [wksp] WKSP
&run genplis
&run genpdlg
SCREENSAVE [wksp]t$pick.ras POPUP 8 10 18 40
&sv [resp]
&sv [exit]
&while &ne [exit] EXIT &do
   POPUP [wksp]t$pick.mnu [resp] 2 8 10 18 40 NONE [resp]
   &if &cn "[resp]" < &do
      &if &eq [resp] ^< &do
         &if &ne [i_cov] 1 &do
            &cv [i_cov] [i_cov] - 6
            &run genpdlg
         &end
      &elseif &eq [resp] V< &do
         &cv [temp1] [i_cov] + 6
         &if &rn [temp1] [i_cov] [numcov] &do
            &sv [i_cov] [temp1]
            &run genpdlg
         &end
      &else
         &length [temp1] "[resp]"
         &value [temp2] [resp] 2 [temp1]
         &if &ne [temp2] [cover] &do
            &sv [cover] [temp2]
            &run genpdlg
         &end
      &end
   &elseif &cn "[resp]" > &do
      &if &eq [resp] ^> &do
         &if &ne [i_dir] 1 &do
            &cv [i_dir] [i_dir] - 6
            &run genpdlg
         &end
      &elseif &eq [resp] V> &do
         &cv [temp1] [i_dir] + 6
         &if &rn [temp1] [i_dir] [numdir] &do
            &sv [i_dir] [temp1]
            &run genpdlg
         &end
      &else
         &length [temp1] [resp]
         &value [temp2] [resp] 2 [temp1]
         &if &cn [temp2] : &do
            &value [temp2] [temp2] 2 4
            & A [temp2]
         &else
            & CD [temp2]
         &end
         &run sysdir [curdir]
         &run genplis
         &run genpdlg
      &end
   &elseif &eq "[resp]" "þ41" &do
      &if &fn "%41\tic.dbf" &do
         &sv [cover] %41
      &else
         &sv 41 [cover]
      &end
   &elseif &eq "[resp]" "þ42" &do
      &if &fn "%42" &do
         & A %42
         &run sysdir [curdir]
         &run genplis
         &run genpdlg
      &else
         &sv 42 [curdir]
      &end
   &elseif &eq "[resp]" "OK" &do
      &if &eq "x[cover]" "x" &do
         &sv [resp] CANCEL
      &else
         &length [temp1] [curdir]
         &value [temp2] [curdir] [temp1] [temp1]
         &if &eq [temp2] \ &do
            &sv [resp] [curdir][cover]
         &else
            &sv [resp] [curdir]\[cover]
         &end
      &end
      &sv [exit] EXIT
   &elseif &eq "[resp]" "CANCEL" &do
      &sv [exit] EXIT
   &else
      &sv [resp] CANCEL
      &sv [exit] EXIT
   &end
&end
SCREENRESTORE [wksp]t$pick.ras
& A [home]
& DEL [wksp]t$pick.ras
& DEL [wksp]t$pick.mnu
& DEL [wksp]t$cov.lis
& DEL [wksp]t$dir.lis
&value %[var] [resp]
&return
In the above text, the edit box field validation character (ASCII 254) is represented by þ. When an up or down arrow is returned, the appropriate index variable is incremented or decremented by the number of rows in the list box; routine "genpdlg" takes care of regenerating the respective list box. When an entry in a list box is pressed, the appropriate edit box is updated; if a directory is changed, the coverage and directory lists are regenerated. A value entered in an edit box is checked for validity. If a new directory is entered, the lists are regenerated and the full path of the new directory is placed in the edit box; note that relative paths (e.g. "..\test1") may be entered. Finally, if "OK" is pressed, the path and coverage name are combined and assigned to the response variable.

Routine "sysdir", which retrieves the current directory, is taken from the "SML Developer's Toolkit":

&routine sysdir

&type "Getting current directory..."
& CD >t$temp.lis
&open t$temp.lis error
&read 2 error
&close
& DEL t$temp.lis
&value %1 2
&return
&label error
&type "SYSDIR: I/O error"
&return

Next: Menus à la Win-Doze


[1]As explained in my previous article, the left-pointing arrow (ASCII 27) followed by two digits constitutes a color escape sequence, and DOS extended characters (176-223) are used for box drawing and button shading.

[2]For reasons of brevity I have omitted field validation, also discussed in my previous article.

[3]One of the major disadvantages of Windows fonts is their inability to represent many DOS characters. That is why I capture POPUP definition code as a bitmap whenever feasible.

[4]A fancier version could limit the number of checkboxes, scrolling any overflow as one would with a list box.

[5]Unless I am mistaken, L's "LR" option did not appear until version 3.4.2. LQ also has an option to list only tables; thus @PICK could easily be expanded to pick either coverages or tables.


Return to ArcTips page