Contents

3.4 VIRTUAL_TERMINAL package

Terminals are notoriously inconsistent when it comes to control codes. They all have different control sequences for clearing the screen and moving the cursor. The VIRTUAL_TERMINAL hides all these differences. The package specification is shown in Listing 19.

3.4.1 Guaranteed Functionality

As you can see, it supports a screen with 79 columns and 23 lines. Everybody knows terminals always have at least 80 columns, but the terminal's response after the 80th column has been printed is uncertain. Some terminals stay at column 80 and overprint the last character entered. Other terminals automatically generate a carriage-return/line-feed sequence and go to the next line. Often the action after the 80th character is programmable. On the other hand, all terminals treat columns 1-79 the same. I was willing to sacrifice 1 column to avoid portability problems. That's also the reason why I used a conservative 23 lines, even though common American terminals have 24 or 25 lines. A 23 line, 79 column screen is the most guaranteed functionality I can expect from every CRT.

The VIRTUAL_TERMINAL has four cursor control (arrow) keys, 20 function keys, and the INSERT, DELETE, TAB and BACK_TAB keys. Not all terminals have all these keys, so provisions have been made for control characters to simulate these 28 special keys. I'm aware that many terminals have more special keys than these, but I'm designing for maximum portability, not maximum performance. Even with this limited capability and designing the package with portability in mind, there are problems porting the VIRTUAL_TERMINAL to DEC terminals on VAX/VMS. (These problems are described in a later section.)

The 20 function keys are converted to a two-character control string. The string begins with CONTROL-F and is followed by a character '1' through '9' or 'A' through 'K'. The remaining eight keys are converted to a single control character. (Refer to the get procedure specification in Listing 19 to see what they are.) Therefore, those 8 keys can be simulated by entering the control code. For example, if a keyboard lacks a DOWN arrow key, the user can use CONTROL-D instead.

3.4.2 Information Hiding

The VIRTUAL_TERMINAL specification give you a procedure that clears the screen, as well as procedures that position the cursor. These procedures hide the control sequences used by the physical terminal from the application program. This information hiding is the key to portability.

The VIRTUAL_TERMINAL needs three services from the underlying operating system. (1) Send a single character to the screen, (2) get a character from the keyboard without echoing it, and (3) find out if there are any unprocessed keystrokes. Every operating system does this differently, and the VIRTUAL_TERMINAL body hides these differences from any application program that uses it.

3.4.2.1 Alsys VIRTUAL_TERMINAL body.

If you are using the Alsys compiler on an IBM PC AT compatible system, these three services are all provided by the Alsys DOS package. The VIRTUAL_TERMINAL package body that works for Alsys programs is shown in Listing 20. The procedure DOS.Display_Char sends a character to the display. The procedures for clearing the screen and moving the cursor can use DOS.Display_Char because it will pass all ASCII characters (even ESC) to the display. (I point this out here because it is different from the Meridian implementation of the DOS interface.)

The function DOS.Read_Kbd_Direct_No_Echo gets characters from the keyboard directly (that is, without interpreting control sequences) and without echoing them to the screen. The get procedure uses this function to fetch keystrokes. It converts the special keys to the standard control keys before passing them to the application program.

The Keyboard_Data_Available function simply renames DOS.Kbd_Data_Available. Notice I didn't use the rename statement because I would have had to have put that in the package specification. I wanted to keep the package specification the same for all implementations, and just change the body. I hope that the global optimizer will remove the double call for me. (It doesn't really matter if it does or not, because human reaction time is much slower than the few wasted microseconds in the double function call.)

The control procedures use the put procedure to send terminal-specific control codes. For example, Clear_Screen uses put to send the control string ASCII.ESC & "[2J" to the screen. The cursor control procedures build similar strings and use put to send them to the display. The exact form of the control string depends on the hardware you are using, so you will probably have to change the escape sequences if you are using a different terminal. That's why you need different bodies for different systems.

The Move_Cursor_To procedure is interesting. It needs to construct the control string ASCII.ESC & '[' & L & ';' & C & 'H' where L is the string representation of the LINE number and C is a string representing the COL. There can be no embedded blanks. You've already seen Figure 17, which shows all the trouble I had to go through to move the cursor before I had the ASCII_UTILITIES.Image function.

The get procedure is a little more complicated than you might expect. It clearly does a lot more than just get the keystroke. It has to check to see if the keystroke is one of the special keys, and if it is then it converts it to the standard control code. On the IBM PC AT the special keys are sent as a two character code. The first code is always 00 and the second code is a unique number. For example, the LEFT arrow key is 00 75. The RIGHT arrow key is 00 77. The get procedure recognizes the 00 as a special-key flag and then reads the second number to find out which key it is.

If the special key is a function key in the range 1..20, the get procedure returns CONTROL_F and stores the number 1..20 in a hidden variable F_KEY. The application program can get this number by calling the function Function_Key. It is possible that the terminal doesn't have function keys, and the user simulated the function key by pressing CONTROL_F followed by a letter or digit. Since the computer is probably faster than the user, there is a danger that the application program will recognize the CONTROL_F input and call Function_Key before the user has a chance to press the letter or digit. That's why F_KEY is reset to 0 each time it is read. If the Function_Key function finds that F_KEY is 0 it knows the user hasn't pressed the letter or digit yet, and waits to read the next keystroke itself.

3.4.2.2 Meridian VIRTUAL_TERMINAL body.

The Meridian version of the package body is shown in Listing 21. It shows how different two implementations on the same machine can be. Instead of a general purpose package like the Alsys DOS package, Meridian sells several special purpose utility packages. If you know a lot about MS-DOS, you can use the INTERRUPT package to access MS-DOS directly. That may be too complicated for some programmers, so Meridian has some less flexible, but simpler, user interface packages. Two of these packages are TTY and CURSOR.

The TTY package calls put to send a single character to the display. It seems to be just like the Alsys DOS.Display_Char, but it isn't. If you try to use TTY.put to send ASCII.ESC & "[2J" to the display to clear the screen, it won't work. It filters out the ESC and just writes [2J on the screen. Similarly, ASCII.ESC & "[C" won't move the cursor right one space. That's why I had to use special routines like TTY.Clear_Screen and CURSOR.Right in the Meridian body.

The TTY.get procedure is similar to the Alsys DOS.Read_Keyboard_Direct_No_Echo function. The TTY.get procedure has two boolean parameters, DIRECT and NO_ECHO, which both must be set to TRUE to achieve the desired effect.

Finally, the TTY.Char_Ready function is effectively renamed to Keyboard_Data_Available by enclosing it in a function body.

If you look at the Move_Cursor_To procedure, and compare it with the Alsys version, you will see an offset has been subtracted. That's because Alsys counts rows and columns starting with 1, and Meridian starts counting at 0. If you carefully compare the two bodies you will find more small differences. The important point to make is that all of these differences would appear in every application program if they weren't carefully confined to the VIRTUAL_TERMINAL body.

3.4.3 Visual Attributes

Terminals usually have different visual attributes. That is, the characters can be bright or dim, blinking or steady, normal or reverse video. An interesting exercise is to make a copy of VIRTUAL_TERMINAL Version 1 and rename the copy Version 2. Then modify Version 2 to include attribute setting procedures called Use_Bright, Use_Dim, Use_Blinking, and so on. You may want to add boolean functions Is_Bright, Is_Dim, and so on, that tell the current status of the visual attributes.

3.4.4 VIRTUAL_TERMINAL Uses

The VIRTUAL_TERMINAL can be used for screen-oriented displays. It is handy whenever you want to move the cursor all over the screen and write text fragments in different places. You'll see an example of this in the FORM_TERMINAL.Create procedure later in this section.

Although the VIRTUAL_TERMINAL can be used as an end product, that isn't really its main use. The VIRTUAL_TERMINAL is most valuable as a portable foundation that can be used as the base for a more useful terminal package. You are about to see two such packages, SCROLL_TERMINAL and FORM_TERMINAL. These two packages are built on top of VIRTUAL_TERMINAL, so it isn't necessary to have different bodies for every implementation. If you can port the VIRTUAL_TERMINAL to work with a different physical terminal or different operating system, then you have ported SCROLL_TERMINAL and FORM_TERMINAL as well.


Contents | Next ...