PRINCIPLES OF
ASSEMBLY LANGUAGE PROGRAMMING
DAVID J. KRUS
ARIZONA STATE UNIVERSITY
CRUISE SCIENTIFIC
Taipei Phoenix Amsterdam
Edited by: James M. Webb
Designed by: Sie Han-Yen
Technical Advisor: Pei Sie-Ka
Information in this document is subject to change without notice. The software described in the text is not copy protected. However, both this manual and the software it describes are protected by copyright and should not be copied without the written consent of the publisher for any purpose other than the purchaser's personal use.
Microsoft is a registered trademark of Microsoft Corporation. Hewlett Packard is a registered trademark of Hewlett Packard Company. The Cruise Fortran and LaserJet Libraries are listed in the Microsoft Language Support Directory and in the Hewlett Packard Peripherals Catalog.
The development of the Fortran callable assembly language subroutines for control of HP LaserJet printers was made possible by the Hewlett Packard Peripherals Developer Program, providing a series of LaserJet printers for the final hardware tests. Sincere thanks are extended to Rachelle Wheatley and Lisa Menas, coordinators of the HP Peripherals Developer Program, for their help and support which contributed to the successful completion of this project.
Copyright 1997 by Cruise Scientific
5F, 11, 10, 329 Nei Hu Road
Taipei, Republic of China
In the Americas address inquiries to Cruise Scientific
3434 West Tulsa Street, Chandler, Arizona 85226-4035
In Europe address inquiries to Cruise Scientific
52 Zamenhofdreef, Utrecht, Netherlands 356JZ
Cover Art by Naoki Yoshimoto.
International Standard Book Number: 0-940325-20-9
Manufactured in the Republic of China
TABLE OF CONTENTS
CHAPTER
ONE
INTRODUCTION 1
Securing and Displaying Information - Windows - Programming at DOS, BIOS and Hardware Levels - The Keyboard - Disks and Disk Files - Coordination of Program Execution - Control of Laser Printers - Getting Started
CHAPTER
TWO
SYSTEM CONFIGURATION AND ORIENTATION IN TIME 7
Type of the Central Processor Unit - System Memory - Upper Memory - Memory Available to Program - Version of the Operating System - Presence of ANSI.SYS Driver - Retrieve Date and Day of the Week - Date Composition - Measure Elapsed Time - Delay Program Continuation
CHAPTER
THREE
THE KEYBOARD 15
Sensing the State of Caps Lock and and Num Lock Keys - Unlocking Caps and Keypad Keys - Redefine Keyboard Key Assignments - Input at the Level of DOS Interrupts - Input at the Level of BIOS Interrupts - The Mapped Keyboard Input - Secure Response to a Query
CHAPTER
FOUR
SCREEN DISPLAYS 22
Clear Screen - Reverse Screen - Blinking Message - Invisible Password Entry - Redefine Cursor Shape - Locate Cursor - Reposition Cursor - Frame a Screen Display Area - Screen Background Textures - Define Attributes of the Scrolled Screen - Cover Page Display - Pagination of Screen Displays - Windows - Read Numbers from Screen - Write Numbers to Screen - Read Text from Screen - Write Text to Screen - Write Text to Screen via DOS Calls - Write Text to Screen via BIOS Calls - Test Strings for Numbers - Fill String with Blank Spaces - Format Strings - Measure Length of Text within String - Text Strings in Upper and Lower Case - Separating Thousands in Numerical Strings
CHAPTER
FIVE
DISKS AND DISK FILES 44
How Many Drives are Present? - Where are We? - Where to Go? - Drive Description - How to Get There? - Change Directory - Remove Directory - Create a New Directory - View Directory - Delete a File - Read Only Files - Make File Invisible to Directory Search
CHAPTER
SIX
PROGRAM OPERATION AND COORDINATION 53
Command Line Arguments - Extract Parameters from the Environment - Invoke Operating System Commands - Catenate Programs - Program Termination
CHAPTER
SEVEN
THE SOUND 58
Sound Alert - Tones - Tunes - Phasers - Sound Effects
CHAPTER
EIGHT
ASYNCHRONOUS COMMUNICATIONS 61
Initialize a Serial Port - Transmit via Serial Port - Receive via Serial Port
CHAPTER
NINE
SELECTORS, MENUS, AND ELECTRONIC BLACKBOARDS 64
Design of the Central Menu - Vertical Free Floating Menus - Selectors - Electronic Blackboards
CHAPTER
TEN
CONTROL OF LASER PRINTERS 70
Opening and Closing Communication Channels - Escape Sequences - Transmission of Characters - Text Transmission - Printing Using the Row Column Grid - The Top Margin - Number of Lines per Page - Reset Margin Settings - Left and Right Margins - Wrap-Around Enable and Disable - Perforation Skip - Backspace - Cursor Movement Across Rows and Columns - Cursor Positioning - Horizontal and Vertical Cursor Movements - Horizontal Pitch - Vertical Spacing - Vertical Motion - Halfway Shifts Up and Down - Subscripts and Superscripts - Underlining - Carriage Return - Line Feed - Carriage Return Followed by Line Feed - Form Feed - Ejection of Page - Reset Printer Setting
CHAPTER
ELEVEN
SPECIAL FEATURES OF THE LASERJET 92
Number of Copies - Orientation - Simplex Printing - Duplex Printing - Binding Edge - Page Break - Paper Trays - Manual Paper Feed - Page Size - Envelopes
CHAPTER
TWELVE
CHARACTER GRAPHICS 99
Horizontal Line - Vertical Line - Coordinates of the XY Graph
CHAPTER
THIRTEEN
RASTER GRAPHIC 103
Resolution of Raster Graphics - Definition of the Paint Brush - Initiation of the Raster Graphics Transmission - Transmission of Raster Graphics - Termination of Raster Graphics - Creation of Raster Graphics - Horizontal Line - Vertical Line - Logo Design - Font Design
APPENDIX
ENVIRONMENT FOR PROGRAM DEVELOPMENT 225
Clavis - ASCII and Upper ASCII Templates - Screen Design Templates - Keyboard Codes - Program Development Aids
TABLES
TABLES FOR PROGRAM DEVELOPMENT 229
The ASCII Chart - The Chart of Design Characters - The BIOS Keyboard Scan Codes - Keyboard Codes Returned by Hardware Port 60h - Selected DOS Interrupts - Selected BIOS Interrupts
GLOSSARY 243
INDEX 259
CHAPTER
1
INTRODUCTION
Design of a modern computer program is a complex task which requires a substantial amount of knowledge of programming in a high level language and typically, also in assembly language. Aside of the development of the functional components of the program, correct structuring of the program components and their integration into a functional whole is crucial to the success of the programming project.
There are several excellent books of the program design, offering invaluable insights into the art of computer programming. Code defensively. Remember the Murhpy's dictum that if anything can go wrong, it will. Learn how to conceal complexity. Each subroutine should do one thing well and hide it, at the same time making the interface between the calling routine and itself highly visible. Find the optimal dividing line between interfacing subroutines, and communicate as few parameters, as possible. Avoid multiple exits from loops. Make sure your variables are initialized. Use meaningful variable names. Eschew temporary variables. Associate the decision points with their corresponding action codes as closely as possible. Do not write long subroutines. If a subroutine stretches over 3 or 4 full screen displays, it is probably too long. Localize input and output subroutines. Identify invalid input and make sure input does not violate program boundaries. Document your program and remember, that a program which runs slowly is far superior to a dysfunctional program running fast. These are maxims to be followed. However, the key difficulty in learning modern program design is that the global program design is developed from the top down, while the actual code has to be written from the bottom up. For the beginning programmer, faced with necessity to code component routines of the programming project, it is easy to loose track of the overriding concerns of the total design and to continue the development of the program along lines which ultimately lead to dysfunctional programs plagued by programming flaws next to impossible to remove.
The organizing principle of the present book is to help the beginning programmer to be aware of the requirements of the overall program design by initially presenting program fragments of calling statements to a variety of library subroutines, to make him or her to be aware of the programming possibilities a comprehensive set of library subroutines offers. Subsequently, the programmer can attempt the development of his or hers own assembly language subroutines, callable from the Fortran programs.
There are several libraries extending Fortran available, the discussion in this book will use the Cruise Fortran Libraries to describe programming concepts and tasks which can be used in the course of the program design and development. The use of the Cruise Libraries does not mean that you should not use other libraries, quite on the contrary. Even though the contents of many dedicated Fortran libraries overlap, typically every library contains many unique subroutines, extending the range of your programming possibilities. By associating several outside libraries with your program design, the range of your programming possibilities widens. Initially, however, let us look at the overall structure of the Cruise Fortran Library.
SECURING AND DISPLAYING INFORMATION
The Cruise Fortran Library replaces Fortran read and write statements with the reads, readn, and writes, writen subroutines to secure and display the textual or numerical information. The reads stands for read string, readn for read number, writes for write string and writen for write number. As different from the Fortran sequential read and write statements, the reads, writes, readn, and writen screen i/o statements can be executed in any order, both temporal and spatial. They also provide for definition of attributes of displayed information and for flexible conversion between numbers as entities which can be algebraically manipulated and entities, suitable for screen displays. They help you to think about numbers in a way you write them on a piece of paper and in a way you calculate them in your head, which are not the same kinds of numbers they appear to be.
The arguments of the reads, readn, writes and writen subroutines have similar syntax. The first argument defines the attributes of the display. The basic attributes are normal, reverse, highlighted, blinking, and their combinations. The following arguments define the offset of the input or the output operations, using the screen row and column coordinates. The fourth argument specifies the format and the last argument provides for transmission of the information to be retrieved or displayed.
WINDOWS
The window subroutine is the key subroutine of modern program design. It was developed following the growing awareness among programmers that the screen displays are windows into the computer memory and that the size of this window, identical to the size of the computer monitor is only one of many possible. By enlarging the area of the memory allocated to the screen displays they realized that more than window can be opened at the same time and by varying the size of these new windows, the modern computer design was born.
When you open a window, you have to first specify its size. The area on the screen, corresponding to the window, is read into memory, saved, and the window surface is cleared. Subsequently, you may write into the window area. When you close the window, the area of the window is read into the memory, saved, and the screen is restored into its original state. All of that is happening at speeds far higher than the normal screen i/o operations, thus creating an illusion that a window is laid on the top of the original screen display. By saving and restoring multiple windows, you may create displays simulating opening of a book and perusing its pages, movable menus, an majority of the paraphernalia typifying the modern screen displays making computing user friendly, flexible, complex, structured, and beautiful.
The reads, readn, writes, and writen are coordinated with the window subroutine to allow you to do all of that easily, efficiently, and sufficiently fast. Remember, it is all done with mirrors and lights, but the key ingredient is the speed which only coding in the assembly language can provide.
PROGRAMMING AT DOS, BIOS, AND
HARDWARE LEVELS
The speed of computing is directly related to the level of interrupts used for development a subroutine. The lower the level, the faster the subroutine. However, there is a price to be paid for the speed and the decision which level to use in which situation is one of the most important to be made in the course of the development of the programming design. There are three main interrupt levels, indicated by the a b or c prefix, as, e.g., a_name, b_name, c_name, where name symbolizes the name of a particular subroutine. The a prefix indicates that the DOS interrupts were used; the b prefix signifies the BIOS interrupts. Subroutines prefixed by the letter c utilize directly the hardware ports. This explicit typing is used when alternate subroutines operating at the different level of interrupts are provided and the decision about the interrupt level to use rests with the programmer.
Most of the subroutines are typed implicitly. In the case of the key subroutines used for design of screen displays, the windows work on the C level, addressing the screen microprocessor directly. The reads, writes, readn and writen subroutines operate either on the B level which is the preferred one, or, sometimes, ascend to the A level, when the nature of the task requires it. This selection of the level is done automatically and does not require the programmer's attention.
If the selection of the level of interrupts to be used is to be done by the programmer, the level of interrupts used is stressed in the description of the alternative subroutine. Thus, e.g., for the screen displays, the subroutine a_pen operates at the A level and the subroutine b_pen at the B level. The alternate subroutines for screen displays carry less overhead than the more general reads, readn, writes, writen subroutines and can be used for more specialized tasks. In selection of the level, one may consider that the direct hardware access subroutines (the C level) are the fastest while the level A subroutines are the slowest with the BIOS B level subroutines located in between. However, as usual, one trades the increased speed of execution for increased hardware dependency. Also, the DOS interrupts provide for more functions than the BIOS interrupts and BIOS interrupts are by far more varied than the interrupts accessing the hardware directly.
THE KEYBOARD
A large group of subroutines are the subroutines for the control and redefinition of keyboard. The keyboard can be imagined as consisting of three levels. The surface level, associated with the DOS keyboard interrupts and the ANSI keyboard driver programmed by the escape sequences, the intermediate level controlled by the BIOS interrupts, and the deep level which can be controlled by the in/out assembly language calls, accessing directly the pins of the keyboard microprocessor.
The surface level is typically used when writing utility programs, operating outside of the main application programs. The intermediate level is the level to use when writing the application programs. This level is impervious to key assignments frequently done by users at the DOS level which could impair the integrity of the input controls within your program. The deep level is seldom used, but some special programs necessitate input control at this level; an example of such an application might be a program for the redefinition of the QUERTY keyboard into the DVORAK keyboard which would be operational even within the application word processing programs.
The subroutines within this group may be classified to two groups. The first group consists of subroutines, controlling the state of the keyboard, as the state of the NUmLock, CapsLock, and Scroll Lock keys. These keys should be reset into the preferred states prior to passing control into the main routines of the program. The second group of subroutines in this category consists of the inkey types of subroutines; main subroutines of this group are the a_key, b_key, and key subroutines. These subroutines operate at the A and B level of interrupts. The subroutines of choice are the b_key and the key. One of the important decisions within this context is whether to use the BIOS interrupt 0h/16h for the control of the standard keyboard, or the BIOS interrupt 10h/16h for the control of the extended keyboard. The main advantage of using the extended keyboard is that you may use the function keys 11 and 12 and assign different function to the gray Insert, Delete, Home, End, Page Up, Page Down, to the arrow cluster of keys, and to the corresponding keys on the numerical keypad. However there is a price to pay in the incompatibility of your program with the older keyboards.
DISKS AND DISK FILES
Subroutines in this group can determine how many drives are installed on the system, can find the location of home directory, change a drive, describe drive in terms of size and used and free disk space, determine a path within a current directory, change a directory, remove directory, create a new directory, display a directory, delete files, and more. These are the necessary tools for development of larger programming projects, consisting of several programs working in concert.
COORDINATION OF PROGRAM EXECUTION
The distinct separate group are subroutines for control of the program execution, facilitating integration of programs and their coordination. The libraries contain subroutines for the program execution control via the environment tokens, switches located on the continuation of the program invoking command, subroutines for catenation of programs, and more.
CONTROL OF LASER PRINTERS
The Hewlett-Packard Printer Control Language is gaining wide acceptance as a standard for control of the printed output. The description of subroutines in the Cruise LaserJet Library is used to demonstrate low level control of the appearance of the output generated by the Laser printers.
GETTING STARTED
The subroutines and functions in the Cruise Fortran Libraries were named and structured as to become permanent additions to the general programming statements; written for computers using INTEL's iAPX 88 / 186 / 286 / 386 / 486 microprocessors and Microsoft DOS operating system, the subroutines were selected with stress on clarity of presentation, general utility, and anticipated frequency of use. Operations already implemented by the Microsoft Fortran compiler were not duplicated; some of them were extended or simplified. Most routines were written in assembly language, but higher languages, especially C, were used whenever appropriate.
The subroutines were originally tailored to the requirements of Microsoft Fortran Version 3.3. Following version of the library was Beta tested on the Version 4.0 and subsequently tuned to the requirements of the Versions 4.1 and 5.1. In most cases you need not worry about version compatibility, however to minimize possible version conflicts, your compiler should be in the compatible mode with the previous version.
After perusing the library subroutines, create a series of miniature Fortran programs and call the subroutines you think you may use in design of your programs. Every subroutine or function described in the following chapters contains a description of its calling conventions and a fragment of the code which surrounds its invoking and allows for demonstration of its essential properties. Subsequently, you may like to begin the development of your own routines; the chapters on the programming in the assembly language and chapters dedicated to interfacing of assembly language subroutines with the Fortran programs provide direction in this respect.
Initially, it is easier to start a programming project by using library subroutines already developed and complement the available subroutines with the custom designed subroutines according to the specific requirements of each design project. Thus, our discussion commences with the description of key routines contained by the Cruise Fortran Libraries.
CHAPTER
2
SYSTEM CONFIGURATION
AND ORIENTATION IN TIME
Few initial statements of some programs may pertain to inquiry about the computing environment. The determination of the type of the central processor unit may be of importance for certain applications. Sometimes, some parts of the program may run efficiently only on the 386 or 486 machines and may be better bypassed if the program is run on the iAPX 88 or 286 machines. Typical problem, related to the type of the microprocessor the program is running on is associated with the use of the sound routines. The sound may sound quite differently if the program is running on the 386 machine then when running on the PC or XT. In this case, the checking of the type of the central processor unit may be of importance. Often, the program may have special requirements about the presence of device handlers in the config.sys file or the type of microprocessor the computer was build around. Following these initial inquiries, the program may require the retrieval of time, as set within the computer. Some of the routines which can be employed to accomplish these tasks are discussed in this chapter.
TYPE OF THE CENTRAL PROCESSOR UNIT
The CPU subroutine determines the type of the central processor unit the program is running on. The subroutine is called as
call cpu(a)
The argument should be dimensioned as character a*1 and returns 1 if the central processing unit is Intel's iAPX 88, 86, 188, 186 or NEC V16 V20 processors. The 2 is returned for iAPX 286, 3 for iAPX 386 central processor unit, 4 for the iAPX 486 or higher. A program fragment, calling the subroutine is presented below.
Listing 2. Fortran Implementation of the CPU Subroutine
------------------------------------------------
character a*1
call cpu(a)
select case(a)
case('1')
write(*,*)'The Central Processing Unit is Intel''s iAPX 8088'
case('2')
write(*,*)'The Central Processing Unit is Intel''s iAPX 286'
case('3')
write(*,*)'The Central Processing Unit is Intel''s iAPX 386'
case('4')
write(*,*)'The Central Processing Unit is Intel''s iAPX 486 or higher'
end select
end
------------------------------------------------
VERSION OF THE OPERATING SYSTEM
The DOS_VER subroutine allows you to determine the version of the operating system. The subroutine is called as
call dos_ver(i,j)
where i returns the verson number and j the revision number. A program fragment, calling the subroutine is presented below.
Listing 2. Version of the Operating System
------------------------------------------------
call dos_ver(i,j)
write(*,*)i,j
end
------------------------------------------------
SYSTEM MEMORY
The MEMORY subroutine allows you to determine whether the system has at least the full amount of the 640K memory. The subroutine is called as
call memory(ix)
and the amount of memory available is returned in the ix parameter. A program fragment, calling the subroutine is presented below.
Listing 2. System Memory
------------------------------------------------
call memory(ix)
write(*,*)ix
end
------------------------------------------------
The subroutine uses the BIOS interrupt 12h which returns the number of contiguous 1k blocks of memory up to 640 K.
UPPER MEMORY
The MEM_UP subroutine determine the available amount of the memory above the 1M. The subroutine is called as
call mem_up(ix)
and returns the amount of the extended memory installed in the ix parameter. A program fragment, calling the subroutine is presented below.
Listing 2. System Memory
------------------------------------------------
call mem_up(ix)
write(*,*)ix
end
------------------------------------------------
The subroutine uses the BIOS interrupt 88h/15h, returning the number of contiguous 1k blocks of RAM above 1M.
MEMORY AVAILABLE TO PROGRAM
To determine the amount of memory available to the program, you may call the MEM subroutine. The mem subroutine uses the DOS interrupt 48h/21h to allocate memory which, on return, contains in the bx register the maximum number of continuous paragraphs available. The subroutine can be called form a Fortran program as
call mem(i)
where i is the amount of available memory in paragraphs. The mem subroutine in no way alters the memory contents. Since every paragraph is 16 bytes long, the conversion into bytes is easy to accomplish, as suggested in the following fragment of Fortran code.
Listing 6. Determining the Anmount of Remaining Memory
------------------------------------------------
call mem(i)
write(*,*)i*16
end
------------------------------------------------
PRESENCE OF ANSI.SYS DRIVER
Sometimes, the programmer may wish to make use of the ansi.sys driver. The ANSI subroutine detects the presence of this driver. The ansi.sys device driver facilitates extended screen and keyboard control by using series of standardized escape sequences. The driver is located in the config.sys file as a statement device=ansi.sys. The subroutine is called as:
call ansi(a)
The argument should be dimensioned as character a*1. The argument returns the plus sign if the driver is present and the minus sign if the driver was not installed at the time the computer was booted. Example of a Fortran program call is listed below.
Listing 2. Fortran Implementation of the ANSI Subroutine
------------------------------------------------
character a*1
call ansi(a)
if(a.eq.'+')then
write(*,*)'Ansi.sys detected'
else
write(*,*)'Ansi.sys absent.'
endif
end
------------------------------------------------
RETRIEVE DATE AND DAY OF THE WEEK
The Microsoft Fortran intrinsic function GETDAT retrieves the year, month, and day. The DAY subroutine returns the current date as set in the operating system, including the day of the week. Parameters of the subroutine are month, day of the month, year, and the day of the week. The subroutine is called as
call day(im,id,iy,iw)
The month and the day are returned as one or two digit integers, the year is a four digit integer, the day of the week is returned as a one digit integer which indexes Sunday with zero, Monday with 1, etc. An example calling this subroutine from the Fortran program is listed below.
Listing 2. Retrieving Date and Day of the Week
------------------------------------------------
call day(im,id,iy,iw)
if(iw.eq.3)write(*,*)'Call Maurice.'
end
------------------------------------------------
DATE COMPOSITION
The DAYS subroutine allows to write the system date with months and days spelled out. It also allows to integrate the date into the surrounding text and add the specification of the locale. The subroutine is called as
call days(len1,len2,datum)
and calculates the relevant lengths within the returned alphanumeric string allowing for writing date without unsightly gaps between months of the varying length. The len1 argument is the length of the datum string up to the end of the year and len2 the length of the datum string up to the end of the day of the week. The datum array is variably dimensioned, the recommended size of the datum array is datum*30. The Fortran calls are presented below.
Listing 2. Composing the Date
------------------------------------------------
character datum*30
call days(len1,len2,datum)
write(*,'(6h Date:,1x,a)')datum(1:len1)
write(*,'(6h Date:,1x,a,1h.)')datum(1:len2)
write(*,*)datum
write(*,'(a,a)')' Minneapolis, Minnesota, ',datum
end
------------------------------------------------
result in date formats (for a certain system date), as tabulated below.
Table 2. Date Formats of the Days Subroutine
------------------------------------------------
Date: January 7, 1991
Date: January 7, 1991; Friday.
January 7, 1991; Friday
Minneapolis, Minnesota, January 7, 1991; Friday
------------------------------------------------
MEASURE ELAPSED TIME
The CENTIS subroutine computes elapsed time in centiseconds. It is used in benchmarking and comparisons of efficiency of alternative routines. The subroutine returns the elapsed time in centiseconds in its argument. It is called as
call centis(itime1)
An example of its usage is given below.
Listing 2. Measuring the Elapsed Time in Centiseconds
------------------------------------------------
call centis(itime1)
... code...
call centis(itime2)
ics=itime2-itime1
write(*,*)ics
------------------------------------------------
The SECNDS subroutine computes elapsed time in seconds. It is used for timing execution of various tasks, such as iteration routines or lengthy calculations, it indicates that something is happening, that the computer didn't just go down, but is working on some task which takes time, and it tells how much time the task takes. This timing also helps the user learn to estimate the time it will take to execute some other lengthy task, by providing experience with timing of similar tasks. An example of its usage follows.
Listing 2. Measuring the Elapsed Time in Seconds
------------------------------------------------
logical*1 terminate \.false.\
ics=0
call secnds(it1)
1 write(*,'(1h+,40x,5hTime:,i7)')ics
call iterate(terminate)
if(terminate)goto 9
call secnds(it2)
ics=it2-it1
goto 1
9 write(*,*)'Iteration terminated.'
------------------------------------------------
DELAY PROGRAM CONTINUATION
The DELAYS subroutine delays execution of the next program statement by the number of deciseconds specified by its argument. The subroutine is called as
call delays(idecs)
The valid time range is 1-600 deciseconds, i.e., from .1 second to one minute. The values of the argument and their corresponding time values are presented below.
Table 2. Translations of Seconds and Minutes to Centiseconds
------------------------------------------------
.5 sec
1.0 sec
3.0 sec
10.0 sec
1.0 min
5 deciseconds
10 deciseconds
30 deciseconds
100 deciseconds
600 deciseconds
An example of the Fortran call follows.
Listing 2. Delaying Execution of the Next Program Statement by Specified Number of Deciseconds
------------------------------------------------
call cls
write(*,*)'Hello'
call delays(30)
call cls
------------------------------------------------
The delays subroutine uses the BIOS interrupt 86h/15h which is available only on the i286 class of computers or higher. An alternative form of this subroutine is the subroutine delay which performs the same task by calling the computer timer directly. The argument of the delay subroutine is calibrated in centiseconds; thus the equivalent of the call of the delays subroutine as call delays(30) would be the call of the delay subroutine as call delay(300).
CHAPTER
3
THE KEYBOARD
The subroutines for the control of the keyboard help to determine the initial status of the keyboard, i.e., are the capitals locked in or what is the status of the numerical keypad. They also allow for redefinition of key assignments. The main subroutines of this group secure input of information into the program. The input can be sceured at the level of DOS interrupts, at the level of BIOS interrupts, or by directly addressing the keyboard hardware ports. It is quite important to decide which level of input from the keyboard to use for specific features of the program. This is the central issue of discussion in this chapter.
TYPE OF KEYBOARD
The extended keyboard was introduced in 1987 and added the keypad keys, several new 'grey' keys and Function 11 and Function 12 keys. In BIOS, new interrupt 10h/16h was added, allowing to acces keystrokes not recognized by the old interrupt 0h/16h. The extended keyboard is available only on the i286 class of computers or higher. To recognize the presence of the extended keyboard, the keyboard subroutine writes a test pattern to the keyboard buffer and reads it back. The subroutine is called as
call keyboard(a)
The keyboard's subroutine single argument returns a plus sign if the extended keyboard was detected, the minus sign if the extended keyboard is not attached to the computer. An example of a Fortran call is
Listing 3. Type of Keyboard
character*1 a
call keyboard(a)
write(*,*)a
end
and the subroutine is listed below.
Listing 3. Type of Keyboard Source Listing
------------------------------------------------
;T H E KEYBOARD S U B R O U T I N E
;call keyboard(a) ;a typical Fortran call
public keyboard
.model large
.stack 64
.code
keyboard proc far
;Save base
push bp
mov bp,sp
;Save registers
push ax
push bx
push cx
push dx
mov bx,[bp+6] ;the last argument
;Write Test Pattern to Keyboard Buffer
mov cx,0ffffh
mov ah,05h
int 16h
mov cx,16 ;set loop counter
do: mov ah,10h
int 16h
cmp ax,0ffffh
je ext
loop do
jmp old
ext: mov al,'+'
mov [bx],al
jmp adieu
old: mov al,'-'
mov [bx],al
adieu: pop dx
pop cx
pop bx
pop ax
pop bp
ret 4
keyboard endp
end
SENSING THE STATE OF CAPS LOCK AND
NUM LOCK KEYS
The C_LOCK subroutine acknowledges whether the CapsLock is engaged. Checking for the engaged CapsLock key before requesting case sensitive input is a considerate practice. The subroutine is called as
call c_lock(x)
where the argument x is declared as character*1. The plus character is returned if the capital letters are locked in, otherwise the minus is reported upon the return of the subroutine to the calling program. A fragment of the program, using this subroutine is presented below.
Listing 3. Sensing whether the CapsLock Key is Engaged
------------------------------------------------
character*1 x
call c_lock(x)
if(x.eq.'+')write(*,*)'Please reset the CapsLock key'
end
------------------------------------------------
The N_LOCK subroutine reports whether the NumLock key is engaged. Most computers boot with the keypad locked in the numerical mode. Applications which use extensively the Insert key often have problems with users pressing the Insert key on the keypad locked in the numerical mode and getting zero inserted instead. Similar problems may be encountered when using the End, Home, Delete, or Page Up and Page Down keys for the program execution control. The subroutine is called as
call n_lock(x)
with the argument x declared as character*1, returning the plus sign if the NumLock key is engaged and the minus sigh otherwise. A program fragment forcing the user to reset the NumLock key is presented below.
Listing 3. Sensing whether the NumLock Key is Engaged
------------------------------------------------
character*1 x
1 call n_lock(x)
if(x.eq.'+')then
call writes('n',23,40,'n','Please reset the NumLock key')
goto 1
endif
------------------------------------------------
Both the c_lock and n_lock subroutine help with the development of the user interfaces sections of the application programs.
UNLOCKING CAPS AND KEYPAD KEYS
The LOCK subroutine gives you direct control over the keyboard Lock keys. Calling the subroutine unlocks the NumLock, CapsLocks, and the Scroll Lock keys. The subroutine is called as
call locks
and has no arguments. A fragment of the program, using this subroutine is presented below.
Listing 3. Unlocking the Lock Keys on the Keyboard
------------------------------------------------
call locks
end
------------------------------------------------
REDEFINE KEYBOARD KEY ASSIGNMENTS
The KEY_DEF subroutine facilitates the assignment of alternate characters, text, or commands to virtually any keyboard key from within the Fortran program. The key_def subroutine can be called in a declarative mode or as a query. The declarative mode, signified by an exclamation mark, appends the carriage return after the text string so that the command gets executed. The query mode does not append the carriage return, so that further specifications may be added to the command before its execution. An example of the declarative mode, defining the ALT D key as a MS-DOS directory command, is
call key_def('0;32,dir/p!')
An example of a query is presented below
call key_def('0;46,copy *.* ?')
assigning the copy all command to the ALT C key. The argument of the key_def subroutine must always be terminated with either a question mark or with an exclamation mark, the scan code must be separated from the text by a comma, and the whole argument must be enclosed in single quotes. An example of a Fortran program, using the key_def subroutine, is listed below.
Listing 3. Assigning Text or Commands to Keyboard Keys
------------------------------------------------
call key_def('0;30,Hi there!') ! Alt A
call key_def('0;46,Copy ?') ! Alt C
stop 'Keys defined.'
end
------------------------------------------------
INPUT AT THE LEVEL OF DOS INTERRUPTS
Subroutine A_KEY accepts input of a single character from the keyboard without depression of the enter key. The keyboard input is via MS-DOS call 7h/21h, which does not echo and ignores the control c sequence. This routine is called as
call a_key(a,'*')
The first argument has to be dimensioned as character*1. The second argument, if entered as '+', translates of the secured characters into the uppercase; if entered as '-', translates the character in the first argument to lowercase. Any other character causes the subroutine to ignore the uppercase or lowercase translation options. A program fragment, illustrating the use of the a_key subroutine is presented below.
Listing 3. The A_KEY Subroutine for Keyboard Input
------------------------------------------------
character*1 a
write(*,*)'Translate input into lowercase'
call a_key(a,'-')
write(*,*)a
write(*,*)'Translate input into uppercase'
call a_key(a,'+')
write(*,*)a
write(*,*)'Input without translation of case'
call a_key(a,'0')
write(*,*)a
end
------------------------------------------------
The a_key subroutine is capable of reading the keyboard key assignments made by the key_def subroutine; it also can recognize the F11 and F12 function keys.
INPUT AT THE LEVEL OF BIOS INTERRUPTS
Subroutine B_KEY accepts input of a single character from the keyboard without depression of the enter key. Typically, it is used used to secure input from the special keys as arrows, page up, page down, home, end, etc. The keyboard input is via BIOS call 0h/16h, which does not echo, ignores the control c sequence and checks for the control break sequence only if the DOS command break on is stored in the environment. Also, the BIOS 0h/16h interrupt does not recognize the F11 and F12 keys. This routine is called as
character a*1
call b_key(a,ix,'*')
where the first argument returns the character typed at the keyboard and the second argument returns the key scan code. The third argument, if specified as '+', translates the input characters into uppercase, if specified as '-', into the lowercase. Any other character in the third argument causes the subroutine to ingnore the case translation options.
Subroutine X_KEY is the upgraded b_key subroutine, calling the 10h/16h interrupt and making possible to secure input from the enhanced keyboard, including the input from the F11 and F12 Keys and over 20 new control and alternate keys combinations.
A tour de force is the subroutine XB_KEY which tests the keyboard and selects the appropriate call, either via the 0h/16h, or the 10h/16h interrupts. Using this subroutine you may write programs using all keys of the extended keyboard which are also compatible with the old PC and XT machines. The new keys should be used as accelerator keys only. In case the program is run on computer with an old keyboard, the program should provide some roundabout path to access all its features. The penalty for using the xb_key subroutine is the increase in size compared to either b_key or the x_key subroutines, and inability to type ahead, since the input buffer must be flushed after the keyboard test. Since this subroutine is typically not used for the input of text, but only to secure input at the decision points, this typically presents no problem. Where the compatibility is at premium, the xb_key subroutine is the subroutine of choice.
An example of the use of the x_key subroutine is listed below
Listing 3. The X_KEY Subroutine for Keyboard Input
------------------------------------------------
character*1 a
1 write(*,*)'Translate input into lowercase'
call x_key(a,ix,'-')
write(*,*)a,ix
write(*,*)'Translate input into uppercase'
call x_key(a,ix,'+')
write(*,*)a,ix
write(*,*)'Input without translation of case'
call x_key(a,ix,'*') !terminates on ctrl break if break on
write(*,*)a,ix
write(*,*)'Press F12'
call x_key(a,ix,'*')
write(*,*)a,ix
write(*,*)'Press CTRL U'
call x_key(a,ix,'*')
write(*,*)ichar(a),ix
write(*,*)'Press ALT U'
call x_key(a,ix,'*')
write(*,*)ichar(a),ix
write(*,*)'Press GRAY INS'
call x_key(a,ix,'*')
write(*,*)ichar(a),ix
write(*,*)'Press INS on the Keypad'
call x_key(a,ix,'*')
write(*,*)ichar(a),ix
write(*,*)'Lock Keyboard and Press INS on the Keypad'
call x_key(a,ix,'*')
write(*,*)ichar(a),ix
goto 1
end
------------------------------------------------
and its source code listing is
Listing 3. Source Code of the X_KEY Subroutine
------------------------------------------------
;T H E X_KEY S U B R O U T I N E
;call x_key(a,ix,'+') ;a typical Fortran call
public x_key
.model large
.stack 64
.code
x_key proc far
;Save base
push bp
mov bp,sp
;Save registers
push ax
push bx
push cx
push dx
mov bx,[bp+6] ;the last argument
mov cl,[bx]
;Input character and scan code
mov ah,10h ;extended keyboard
int 16h
;Conversions to upper or lower case
cmp cl,'+'
je uppercase
cmp cl,'-'
je lowercase
jmp deposit
uppercase label near
cmp al,'a'
jb deposit
cmp al,'z'
ja deposit
and al,01011111b ;mask out the fifth bit
jmp deposit
lowercase label near
cmp al,'A'
jb deposit
cmp al,'Z'
ja deposit
or al,00100000b ;convert to lowercase
deposit label near
mov bx,[bp+14] ;first argument address
mov [bx],al ;ASCII char out
mov si,[bp+10] ;address of ix
mov [si],ah ;scan code out
;Return
pop dx
pop cx
pop bx
pop ax
pop bp
ret 12
x_key endp
end
------------------------------------------------
THE MAPPED KEYBOARD INPUT
The KEY subroutine secures input of both upper and lower parts of the ASCII character set by mapping selected characters from the upper ASCII set into the alternate keys of the keyboard. The chart of the mapped keys can be overlayed over the current screen display by pressing a selectable key on the keyboard, typically the F1 or F2 key. The display of the chart is context sensitive. The selection of the mapped characters and their assignments are quite general and confirm to the requirements of most programming designs. The alt 0, alt 1 and alt 2 keys add the capability to use the zero, 1/2, and 2 powers. The Greek characters are mapped into their corresponding Latin character alt keys and the scientific notation characters between ASCII 224 and ASCII 255 are mnemonically assigned to alternate keys of the keyboard, as, e.g., the infinity sign being mapped to alt i, and the square root sign into the alt r. The key subroutine is called as
call key(a,ix,'*',map)
The first three arguments of the key subroutine are the same as of the b_key or b_keys subroutines. The first argument returns the character typed, the second argument returns the scan code. The scan codes returned by the BIOS interrupt 0h/16h are listed in the last chapter, describing the programming environment utilities, including the relevant tables. The third argument, if set to '+", translates the input into uppercase, if set to '-', into the lowercase. The last argument defines the key to be used as the 'help' key displaying the chart of the mapped characters. A program fragment, using the key subroutine is listed below.
Listing 3. The KEY Subroutine for Keyboard Input
------------------------------------------------
character a*1
call key(a,ix,'*',59) !The help key is F1
if(ix.eq.1)stop
call writes('r',7,7,'n',a)
---------------------------------------------
The Key subroutine is typically called from the Reads subroutine.
SECURE RESPONSE TO A QUERY
The QUERY subroutine saves coding of several Fortran statements frequently used to obtain keyboard input, check for input errors, and branch according to the value of the input character. The argument field of the query subroutine consists of two parts. The first part contains an integer variable returning the ordinal position of the selected character located in the second argument field. The second part contains list of tokens to be matched. All tokens must be in lowercase. The Fortran call of this subroutine is as, e.g.,
call query(ians,'a,b')
Following the call, a match is attempted between a keyboard reply (no carriage return, inkey type) and the argument list. If no match is found, the query routine expects additional input. Any uppercase input is translated into lowercase. Single digit numbers such as 0,1,2,3,4,5,6,7,8,9 can be used. To specify keys with no corresponding keyboard characters, associate the BIOS keyboard code with an ASCII character code; thus, e.g., the function key F1 with the BIOS keyboard code of 59 translates into a semicolon. The query subroutine also checks for the escape key. If the escape key is depressed, the ians argument will be returned as equal to zero. An example of a Fortran program using this subroutine is listed below
Listing 3. The QUERY Subroutine for Selection from a List of Alternatives
------------------------------------------------
write(*,*)'Select alternative A B or C'
call query(ians,'a,b,c')
select case(ians)
case(0)
stop
case(1)
write(*,*)'The first decision point'
case(2)
write(*,*)'The second decision point'
case(3)
write(*,*)'The third decision point'
end select
------------------------------------------------
The source code for the Query subroutine is
Listing 3. Source Code of the QUERY Subroutine
------------------------------------------------
subroutine query(iorder,a)
character *(*)a,x*1
max=length(a)
1 call b_key(x,iterm,'-')
if(iterm.eq.1)then !Escape
iorder=0
return
endif
iorder=0
do i=1,max
if(a(i:i).ne.',')iorder=iorder+1
if(a(i:i).eq.x)return
if(ichar(x).eq.0.and.ichar(a(i:i)).eq.iterm)retu rn
end do
call tone 0
goto 1
end
------------------------------------------------
CHAPTER
4
SCREEN DISPLAYS
The subroutines controlling the screen displays are the principal tools of the program development. In this chapter we are going to discuss subroutines for displaying information on the computer's screen, reading information displayed on the screen, and designing screen displays. The characters, used in screen displays and their corresponding codes are listed in Tables on the end of the book. Aside of the regular letters of the alphabet, they also include special characters, Greek characters, characters for drawing lines and borders and characters for creation of special graphic effects.
Each character displayed in the video buffer contains two bytes of information. The ASCII value of the character and the character's display attribute. The attribute byte determines whether a character is highlighted, blinking, underlined, or in reverse video. The subroutines discussed frequently provide for specification of the attribute byte of the displayed characters in the first parameter of their arguments.
The key procedures for programming screen displays are the window, reads, readn, writes, writen subroutines. They are complemented by the subroutines for framing screen display areas, creation of screen background textures, and by numerous auxiliary subroutines.
CLEAR SCREEN
The CLS subroutine clears the screen by scrolling it up and positioning the cursor to the upper left corner. The Fortran call is
call cls
The cls subroutine scrolls the screen and repositions the cursor by using the BIOS interrupt 10h. This routine is the Fortran callable counterpart of the MS-DOS keyboard callable utility cls.
A variation of the cls subroutine is the subroutine SCD. The SCreen Down subroutine is functionally equivalent to the CLS subroutine. However, it scrolls the screen down and positions the cursor to the lower left corner of the screen. The subsequent screen displays will be scrolled from the bottom to the top of the screen. The subroutine is called from a the Fortran program as
call scd
and has no arguments.
REVERSE SCREEN
The REVERS subroutine changes the surface of a video screen into the reverse video and back to the normal state. This subroutine has two parameters. The plus sign to change screen to a reverse video and the minus sign to obverse screen to the normal state. Example of the Fortran call is
call revers('+')
Sample usage is presented in Listing 4..
Listing 4. Reverse Video Screen
------------------------------------------------
character*1 a
write(*,*)' Revers <+/->'
read(*,'(a)')a
call cls
call revers(a)
write(*,*)' Hello world'
call tab
call scd
write(*,'(24(1x,77(1h*)/))')
call tab
------------------------------------------------
BLINKING MESSAGE
The BLINK subroutine makes the text blink. The text should be terminated with another call blink statement, this time with the minus argument. To remove the blinking text, overwrite it with blanks. The Fortran call is
call blink('+')
An example of its use is listed below.
Listing 4. Blinking Messages
------------------------------------------------
call blink('+')
write(*,*)' Hello world'
call blink('-')
------------------------------------------------
The Blink subroutine operates on the level of DOS interrupts.
INVISIBLE PASSWORD ENTRY
The SECRET subroutine turns the screen display off. It can be used in situations when you want to secure input from some peripheral device as, e.g., the keyboard, without echoing the input on the screen. This subroutine is useful when entering a secret password. Another use of this subroutine is in situations when a graphic display fills the whole screen and a response to a query would write over some part of the display.
The +/- [on/off] switch constitutes the only parameter. The plus sign turns the screen display off. The minus sign restores the normal screen display. The cursor is not turned off by this subroutine. To totally blank the screen, you have also to call the cursor('i') subroutine. Example of the Fortran call is
call secret ('+')
An example of the use of this subroutine is listed below.
Listing 4. Invisible Writing into the Screen
------------------------------------------------
character string*6,mask*6
mask='sesame'
write(*,'(a\)')' Enter password: '
call secret('+')
read(*,'(a)')string
call secret('-')
if(string.eq.mask)then
write(*,*)'Access permitted.'
else
write(*,*)'Access denied.'
endif
------------------------------------------------
REDEFINE CURSOR SHAPE
The CURSOR subroutine, although simple, is indispensable for the modern screen design. To make the cursor take on a different shape when a different type of input is expected, or to make the cursor disappear, is one of the most mundane programming chores. The subroutine is called as, e.g.,
call cursor('i')
with possible values of the argument summarized in Table 4..
Table 4. Definitions of Cursor Shape
[i] invisible
[h] hair
[l] line
[d] dash
[c] cube
[b] block
[s] split
[u] underlined
makes cursor disappear
a thin bottom line
a double line cursor
a heavy line cursor
a small square
a large rectangle
a top-bottom thin line cursor
an exclamation mark shape
LOCATE CURSOR
The GETCUR subroutine returns the screen coordinates of the current cursor location. The determination of these coordinates is necessary for the flexible screen display control. This subroutine is called from the Fortran program as
call getcur(i,j)
The integer parameters i and j are the screen row and column cursor coordinates. An example of the use of this subroutine is given below.
Listing 4. Location of the Cursor
------------------------------------------------
call getcur(i,j)
write(*,'(a,2i3)')' Cursor location:',i,j
call tab
end
------------------------------------------------
REPOSITION CURSOR
The SETCUR subroutine repositions cursor to a new location on the screen. The subroutine is called as
call setcur(i,j)
The integer parameters i and j are row and column screen coordinates where the cursor is to be repositioned. The setcur subroutine is called in an absolute manner as, e.g.,
call setcur(12,30)
which repositions the cursor to a certain location on the screen. The relative call to this subroutine has to be preceded by the call to the getcur subroutine, as shown in the example below.
Listing 4. Location of the Cursor
------------------------------------------------
call getcur(i,j)
call setcur(i+3,j+7)
write(*,'(a,2i3)')' The Offset of this Message is',i+3,j+7'
------------------------------------------------
FRAME A SCREEN DISPLAY AREA
The square areas, delineated by single or double line borders, are the building blocks of the modern screen designs. Calling the FRAME subroutine will create a box on the screen, optionally in reverse video. The arguments of the Frame subroutine are similar to those of the Window subroutine and the Frame subroutine should be used whenever possible in lieu of the Windows subroutine, since it carries substantially lower overhead. The use of the Frame subroutine is mandatory in cases of chaining a number of boxes in circular menus which can recur dozens or hundreds of times, since the repeated use of the Windows subroutine will eventually run out of the memory storage. Obviously, the Frame subroutine does not preserve the content of the overlaid area on the screen, as the Window subroutine. Often, the Window and the Frame subroutines can be combined. The Window subroutine is called first to overlay the screen display, and the window is subdivided into the areas dictated by its design by the Frame subroutine.
To position the box on the screen, first, define its appearance by using one of the q r t s arguments, described below, and then define its coordinates by using the standard screen grid indexed from 0 to 24 by its rows and from 0 to 79 by its columns. The Fortran call is
call frame('x',i,j,k,l)
where x determines the character of the border lines and i,j,k,l are the coordinates of the upper left and the lower right corner of the box. The values of the x parameter are 'b' for bold(thick) borders, 'c' for borders combining double and single line border, 'q' for the double line borders, 'r' for the double line border and the reverse video fill, 's' for the single line border, and 't' for the single line border and the reverse video fill. A fragment of a program, calling the frame subroutine, is listed below.
Listing 4. Framing a Screen Area
------------------------------------------------
call cursor('i')
call cls
call frame('q', 0, 0,23,79)
call frame('r',10,15,12,45)
call frame('s',17, 8,20,60)
call frame('t',17, 8,20,60)
call tab
call setcur(0,0)
------------------------------------------------
SCREEN BACKGROUND TEXTURES
The SHADOW subroutine changes the ordinarily black screen background into mellow textures, well suited to be superimposed on screen windows or for subsequent framing. Three texture types are available. These types are selected by calling the shadow subroutine as shown in the following statements.
call shadow('dark',i,j,k,l)
call shadow('medium',i,j,k,l)
call shadow('light',i,j,k,l)
where the i,j,k,and l arguments define the screen coordinates of the upper left and bottom right corners of the area to be painted over. The shadow subroutine can well complement the window subroutine or be complemented by the frame subroutine, as shown in the example of Fortran implementation of the window, frame, and shadow subroutines below.
Listing 4. Framed Screen Areas With Textured Background
------------------------------------------------
call cursor('i')
call window('s',0,0,24,79)
call shadow('dark',1,1,23,78)
call tab
call shadow('medium',15,15,20,60)
call frame('d',15,15,20,60)
call tab
call shadow('light',17,17,19,70)
call frame('d',17,17,19,70)
call tab
call shut
call cls
call cursor('h')
------------------------------------------------
In the above example, notice that the shadow routine painting the suface of the window must be called after the window subroutine and must be dimensioned to cover a smaller area than the window subroutine not to overwrite the borders of the window. On the other hand, the frame subroutine should be either called after the shadow subroutine, or dimensioned with coordinates not overlapping with the coordinates of the shadow subroutine.
DEFINE ATTRIBUTES OF THE SCROLLED SCREEN
The SCROLL subroutine imposes a new attribute on selected areas of the screen surface. In some respects, this operation opens a "window" on the video screen. However, as contrasted with the window subroutine, the screen area painted over is not saved and cannot be subsequently restored. It is also possible to use the scroll subroutine just for clearing of selected screen areas without changing the screen surface attribute. The first parameter of the scroll subroutine is the attribute to be imposed on the scrolled screen area. Three attributes are supported. Reverse [r], underlined [u] and normal [n]. The next four parameters are the left upper row and column [i,j] and the right lower row and column [k,l] coordinates. The example of Fortran call is
call scroll('r',0,0,15,79)
A sample Fortran program, demonstrating the properties of the scroll subroutine is presented below.
Listing 4. Scrolling the Screen Areas
------------------------------------------------
character*1 x
write(*,*)'Define the Scroll Attribute <r,u,n>'
read(*,'(a)')x
write(*,*)'Coordinates of the Upper Left and Lower Right Corners:'
read(*,*)i,j,k,l
call scroll(x,i,j,k,l)
------------------------------------------------
COVER PAGE DISPLAY
The COVER subroutine composes full screen displays using characters from the Cruise Font Library. This subroutine has two arguments. The first argument is the character to use for the writing letters, selected from the font library. Letters in the font library were constructed by using character 219. Using another fill characters, as # 221 or 240 can improve the appearance of the front page of your program. The second argument of the subroutine is the file name, containing your front page display. The subroutine is called, as, e.g.,
call cover(x,'front_disp.fnt')
and can be preceded by calls to screen formatting subroutines such as the cls, shadow or frame subroutines. An example of a program, demonstrating how the selection of the fill character can change the appearance of the font is listed below.
Listing 4. Cover Page Displays
------------------------------------------------
character x*1
x=char(219)
call cls
call frame('s',0,0,23,79)
call cover(x,'front_disp.fnt')
call tab
------------------------------------------------
PAGINATION OF SCREEN DISPLAYS
Subroutine TAB stops the display until a key is depressed. It also checks whether the key depressed generates an extended keyboard code, preceded by the ASCII null character. If detected, the leading null character is deleted. Thus, e.g., pressing the space bar or the down arrow results in the same behavior of the program. The tab routine can be called as
call tab
Functionally similar is the VTAB subroutine. However, the message "Press a key to continue," is displayed in the lower right corner of the screen when this subroutine is invoked. The vtab subroutine is called as
call vtab
Subroutine PAGES stops the display until a key is depressed. The right-adjusted message is displayed in the lower right corner of the screen. When any key is touched, the execution of the program continues. This routine can be called as
call pages('message$,iterm)
where the iterm parameter returns the termination code of the keyboard input. The termination code allows to determine whether, e.g., the input was terminated by the carriage return, or by the insert key, which may be of importance in some programming contexts. However, the most important use of the iterm parameter is to detect the use of the escape key. Illustration of the use of the subroutines for pagination of screen displays is presented below.
Listing 4. Pagination of Screen Displays
------------------------------------------------
1 call cls
write(*,*)'Hello'
call tab
write(*,*)'Hello world'
call vtab
call pages('Turn page$',iterm)
if(iterm.eq.1)stop
goto 1
end
------------------------------------------------
WINDOWS
The WINDOW subroutine can simultaneously open multiple overlapping windows on the screen surface. The window subroutine has five arguments. The first argument describes the surface of the window and the window borders. The possible values of this argument are 'q r s t u v'. If specified as 'q', the window is opened with the normal surface bordered by the double line frame. This is the default switch and entering any other character aside of the q r s t u v group triggers this switch. This permits renaming the q switch as n, a better mnemonics for 'normal'. If specified as 'r', the window surface is painted as the reverse video with double borders. The 's' switch defines the window as having the normal surface and single line borders. The 't' switch opens the window in reverse video, surrounded by a frame consisting of single lines. The 'u' switch opens a window with the normal surface and no borders and the 'v' switch, also deleting the borders, opens the window in reverse video. In most situations, the simplest and aesthetically pleasing displays are achieved by using the s or v switches.
The following arguments specify the coordinates of upper left and lower right corners of the window. The standard screen grid is used for the description of the window location. The grid has the origin 0,0 in the upper left screen corner. The screen has 24 lines and 80 columns, so the right lower corner of the screen has the 24,79 row, column coordinates. The window coordinates may be any within these limits. The Fortran call to the window subroutine is
call window('x',i,j,k,l)
To close the window, call the SHUT subroutine as
call shut
The SHUT subroutine has no arguments and should be called as many times as the window subroutine, to close the windows opened and to restore the screen to its original state. Both the window and the shut subroutines use the direct screen writes, bypassing both the DOS and the BIOS interrupts when saving and restoring the screen surface. They are fast and permit for about six full screens to overlap. An example of the Fortran implementation follows. It demonstrates opening of several stacked windows preserving the information previously stored on the screen, originally filled with dots. Answering in the affirmative to the query whether to close the window removes the previously opened windows in the reverse order, restoring their original surfaces, and, finally, uncovering the intact original screen surface.
Listing 4. The Window and Shut Subroutines
------------------------------------------------
character x*1
call cursor('i')
call cls
write(*,'(2000(1h.))')
n=0
1 n=n+1
write(*,*)
call b_pen(22,0,' $')
call b_pen(22,0,'Enter window attribute <q,r,s,t> and coordinates$')
read(*,*,err=9)x,i,j,k,l
call window(x,i,j,k,l)
call b_pen(i+1,j+1,'Hello$')
call b_pen(24,50,'Close window? <y,n>$')
call query(ians,'y,n')
if(ians.eq.1)goto 3
goto 1
3 n=n-1
do i=1,n
call shut
call tab
end do
9 call cls
call cursor('h')
end
------------------------------------------------
READ NUMBERS FROM SCREEN
The READN (READ Number) subroutine reads a number from a screen or a screen window and translates the number from a character representation into an internal computer representation of a number. The readn subroutine is called as
call readn('x',i,j,format,an,iterm,'0,1,82') !BREAK,ESCAPE,INSERT
where 'x' defines the text attributes of a number, as it is read from the screen. The attributes are coded as normal ('n'), reverse video ('r'), blinking ('b'), highlighted ('h'), attention getting, i.e. highlighted and blinking ('a'), underlined ('u') and invisible ('i').
The i and j are the offset coordinates of the screen location the number is going to be read from. The coordinates use the standard screen row, column grid with origin located at 0,0 and lower right corner located at 24,79.
The fourth parameter defines the format of the number. If set to 0.0, it will cause the number to be read in an inkey fashion, securing input of single digit numbers without having to depress the termination key. Formats where the period is followed by zero will read the integer numbers. However, the read number is always encoded as an floating point number. The valid format is any two number combination, separated by a period, within the 0.0 and 9.9 interval. However, only integer numbers smaller than 999,999 (formatted as 6.0) and floating point numbers smaller than 9999.999 (formatted as 4.3) are encoded reliably. Outside of these limits, the floating point numbers may be corrupted past the third decimal place and the integer numbers greater than one million will be unreliable. The encoded number is returned in the last but one argument, always as a floating point number.
The iterm parameter returns the scan code of the key the input was terminated with. If the iterm parameter is returned is equal to -1, a blank string was read. The blank input string typically indicates a missing value. The subroutine checks for the termination codes, listed following the iterm parameter. At least one termination code (e.g., '0' for the CTRL BREAK) must be listed. Up to 64 termination codes can be listed. The readn subroutine recognizes the backspace and delete keys. A program fragment, using the readn subroutine is listed below.
Listing 4. Reading Numbers from the Screen
------------------------------------------------
call cursor('i')
call cls
an=0.0
1 call writen('r',20,0,10.3,an)
call readn('r',10,15,5.3,an,iterm,'0,1,82') !Break,Escape,Insert
if(iterm.eq. 0)goto 9
if(iterm.eq. 1)goto 9
if(iterm.eq.82)goto 9
goto 1
9 call cursor('h')
end
------------------------------------------------
The source code listing for the readn subroutine is
Listing 4. Source Code for the Readn Subroutine
------------------------------------------------
subroutine readn(a,i,j,format,an,iterm,list)
integer*2 itoken(64)
character s*20,z*1,x*1,a*1,slip*3,list *(*)
logical*1 floating,integer,inkey
x=a
if(a.eq.'a')x='n'
if(a.eq.'b')x='b'
if(a.eq.'c')x='r'
if(a.eq.'d')x='h'
if(a.eq.'e')x='a'
if(a.eq.'f')x='u'
if(a.eq.'g')x='i'
2 inkey=.false.
max=format
maxx=anint((format-max)*10.0)
if(max+maxx.gt.20)then
pause 'Format error in CS subroutine readn'
return
endif
if(format.eq.0.0)then
inkey=.true.
max=1
maxx=0
end if
"===DECODE LIST
call blank(slip)
ix=0
ntokens=1
do is=1,len_trim(list)
if(list(is:is).eq.',')then
read(slip,'(i3)')itoken(ntokens)
ix=0
ntokens=ntokens+1
call blank(slip)
cycle
end if
ix=ix+1
slip(ix:ix)=list(is:is)
end do
read(slip,'(i3)')itoken(ntokens)
an=0
call blank(s)
index=1
iterm=0
1 call b_key(z,iterm,'*')
do is=1,ntokens
if(iterm.eq.itoken(is))return
end do
if(index.eq.1.and.iterm.eq.28)then !Missing value
iterm=-1
return
endif
if(iterm.eq.28)goto 5 !CR
if(iterm.eq.14)goto 4 !BS
if(z.eq.'-'.or.z.eq.'+')then
"max=max+1
goto 3
end if
if(z.eq.'.')then
max=max+maxx+1
goto 3
end if
if(a.ge.'a'.and.a.le.'g')goto 3 !Alphanumeric mode
if(z.lt.'0'.or.z.gt.'9')then
call tone 0
goto 1
endif
"_____Echo character
3 index=index+1
if(index.gt.max+1)then
call tone 0
index=index-1
goto 1
endif
call writes(x,i,j,'x',z)
j=j+1
s(index:index)=z
"_____Inkey type
if(inkey)then
iterm=28
goto 5
endif
4 if(iterm.eq.14)then !Backspace
if(index.lt.2)then
call tone 0
index=1
goto 1
endif
index=index-1
j=j-1
if(s(index+1:index+1).eq.'-')max=max-1
if(s(index+1:index+1).eq.'.')max=max-maxx-1
s(index+1:index+1)=1h
call pen(i,j,' $')
goto 1
endif
goto 1
"_____TRANSLATE STRING TO NUMBER
5 floating=.false.
integer=.true.
do ix=1,length(s)
if(s(ix:ix).eq.'.')then
floating=.true.
integer=.false.
end if
end do
if(integer)then
read(s,'(i10)',err=9)num
an=num
elseif(floating)then
read(s,'(f15.2)',err=9)an
endif
return
9 call tone 0 !Internal error read
pause 'Internal error read in CS subroutine readn'
end
------------------------------------------------
WRITE NUMBERS TO SCREEN
The WRITEN (WRITE Number) subroutine translates a number in an internal computer representation into a character string. The written subroutine is called as
call writen('x',i,j,format,an)
The first parameter defines the text attributes of a number, the next two parameters are the screen coordinates, the fourth parameter is the format of the number and the last parameter is the number to be converted into a string and written to any place on screen. An example of an call to the written subroutine is
Listing 4. Writing Numbers to Screen
-----------------------------------------------
------------------------------------------------
"an=99999.99 !Max number
call cls
an=12345.67
call writen('R',6,10,14.2,an)
call tab
end
The text attributes are [n]ormal, [r]everse video, [b]linking, [h]ighlighted, [a]ttention getting (highlighted and blinking), [u]nderlined and [i]nvisible. The attributes are case sensitive. If specified in capital letters, the comma, separating thousands, will be inserted if output number is 1,000 or greater. In this case, make sure that the format is wide enough to accomodate the expanded length of the number.
The screen coordinates may vary between 0 and 24 for rows and 0 and 79 for columns. The format follows the format conventions for a floating point number, however is not preceded by the letter f. Thus, e.g.,
call writen(i,j,6.3,an)
defines a number with five digits, containing a decimal point with three digits following. The format
call writen(i,j,5.0,an)
defines an integer up to five digits. Decimal point is not printed. The largest format permitted is 15.5, the smallest formats are 2.1 and 1.0. The format must be always a floating point number, for the above example, specifying the format as 5 instead of 5.0 would return an error message.
The source code for the writen subroutine is listed below.
Listing 4. Source Code for the Writen Subroutine
-----------------------------------------------
subroutine writen(x,i,j,f,an)
character string*20,out*20,x*1
logical*1 integ,comma
integ=.false.
comma=.false.
if(ichar(x).gt.64.and.ichar(x).lt.91)comma=.true . !Uppercase=insert commas
nw=0
nf=0
nw=f
nf=amod(f,1.0)*10.0+.5
if(nw.gt.15)goto 9
if(nf.gt. 5)goto 9
if(nf.le.0)integ=.true.
call blank(string)
if(integ)then
in=an
write(string,'(i15)',err=9)in
goto 1
endif
if(nf.eq.1)then
write(string,'(f15.1)',err=9)an
elseif(nf.eq.2)then
write(string,'(f15.2)',err=9)an
elseif(nf.eq.3)then
write(string,'(f15.3)',err=9)an
elseif(nf.eq.4)then
write(string,'(f15.4)',err=9)an
elseif(nf.eq.5)then
write(string,'(f15.5)',err=9)an
endif
1 call blank(out)
if(comma)call thousand(string)
if(nw.lt.1)goto 9
out(1:nw)=string(15-nw+1:15)
out(nw+1:nw+1)=1h$
"_____Write number out
call stylus(x,i,j,out)
return
9 write(*,*)'Error in CS FOR.LIB subroutine writen'
end
------------------------------------------------
READ TEXT FROM SCREEN
The READS (READ String) subroutine reads text from any place on screen and echoes the characters read in a display style defined by their corresponding attribute. The subroutine can be also invoked in an 'inkey' mode. The subroutine is called as
call reads('x',i,j,format,string,iterm,'0,1,82') !BREAK,ESCAPE,INSERT
where 'x' defines the text attribute, i,j, are screen coordinates followed by the format parameter, a variable dimensioned string, and the iterm parameter which upon return contains information how the string was terminated.
The text attributes can be defined as normal ('n'), reverse video ('r'), blinking ('b'), highlighted ('h'), attention getting, i.e. highlighted and blinking ('a'), underlined ('u') and invisible ('i') attributes.
The i,j parameters define the offset of the display on the standard screen grid with the 0,0 origin in the upper left corner, stretching to the 24,79 coordinate point in the lower right corner of the screen.
The format parameter has to be specified as a floating point number and specifies the maximum length of a string. If this maximum length is exceeded at input, cursor stops advancing and a soft murmur sound sounds. The number following the decimal point (1 to 9) can be used to define the width of the right margin of the string which can be optionally overriden by pressing the tab key on the keyboard. The margin can be up to 9 characters wide. If the format is specified as 0.0, the reads subroutine append the carriage return and functions in an 'inkey' mode.
The subroutine checks for termination codes, listed following the iterm parameter. At least one and up to 64 termination codes may be listed. The termination codes returned by the iterm parameter facilitate development of screen displays and parsing algorithms. If a blank string was entered, the iterm parameter is returned as equal to -1.
The prompt of the reads subroutine is an asterisks. Pressing the grey asterisk key on the keypad displays a chart of extended ASCII characters which may be used as input. A fragment of code, using the reads subroutine is listed below.
Listing 4. Reading Text from the Screen
------------------------------------------------
character string*50
call cursor('i')
call cls
1 call writes('r',20,0,'l',string)
call reads('r',10,15,5.9,string,iterm,'0,1,82') !Break,Escape,Insert
if(iterm.eq. 0)goto 9
if(iterm.eq. 1)goto 9
if(iterm.eq.82)goto 9
goto 1
9 call cursor('h')
end
------------------------------------------------
Source code of the reads subroutine is
Listing 4. Source Code of the Reads Subroutine
------------------------------------------------
subroutine reads(x,i,j,format,s,iterm,list)
integer*2 itoken(64)
character * (*) s
character * (*) list
character x*1,z*1,slip*3
logical*1 inkey
call writes(x,i,j,'x','*')
"===Format decoding
inkey=.false.
if(format.gt.0.0)then
max=format
maxx=anint((format-max)*10.0)
maxxx=max+maxx
if(maxxx.gt.len(s))then
write(*,*)'Format is greater than lenght of the string.'
pause 'Error in CS subroutine reads'
goto 9
endif
elseif(format.eq.0.0)then
inkey=.true.
max=1
elseif(format.lt.0.0)then
pause 'Format error in CS subroutine reads'
goto 9
end if
"===DECODE LIST
call blank(slip)
ix=0
ntokens=1
do is=1,len_trim(list)
if(list(is:is).eq.',')then
read(slip,'(i3)')itoken(ntokens)
ix=0
ntokens=ntokens+1
call blank(slip)
cycle
end if
ix=ix+1
slip(ix:ix)=list(is:is)
end do
read(slip,'(i3)')itoken(ntokens)
iterm=0
index=0
1 call key(z,iterm,'*',55) !HELP KEY IS GREY STAR
do is=1,ntokens
if(iterm.eq.itoken(is))goto 9
end do
if(index.eq.0.and.iterm.eq.28)then !Missing value
iterm=-1
goto 9
endif
if(iterm.eq.28)goto 9 !CR
if(iterm.eq.14)goto 4 !BS
if(iterm.eq.15)then
max=maxxx !TAB
goto 1
end if
"===Check if Characters are ASCII
do is=1,57 !Past space are control codes
if(iterm.eq.is)goto 3
end do
call tone 0
goto 1
"_____Echo character
3 index=index+1
if(index.gt.max)then
call tone 0
index=index-1
goto 1
endif
call writes(x,i,j,'x',z)
j=j+1
s(index:index)=z
"_____Inkey type
if(inkey)then
iterm=28
goto 9
endif
4 if(iterm.eq.14)then !Backspace
if(index.lt.1)then
call tone 0
index=0
goto 1
endif
index=index-1
j=j-1
s(index+1:index+1)=0
call writes('x',i,j,'x',' ')
goto 1
endif
goto 1
9 return
end
------------------------------------------------
WRITE TEXT TO SCREEN
The WRITES (WRITE String) subroutine positions string on any place on the screen. It provides for definition of text attributes and for string formatting. The subroutine is called from a Fortran program as
call writes('x',i,j,format,string)
where x contains information about text attributes, i,j, are the screen coordinates followed by the format parameter and a variable dimensioned string. The i parameter specifies the row of the screen where the text is to originate.
The text attributes can be defined as normal [n], reverse video [r], blinking [b], highlighted [h], attention getting (blinking and highlighted) [a], underlined [u], and invisible [i].
The format parameter specifies the formatting of the text within the displayed string and can be written as as 'l' for left justified, 'c' for centered, and 'r' for right justified. Any other character specified skips the string formatting routines.
The j parameter, if used together with the 'l' format defines the left margin of the text line. Used in conjunction with the 'c' or 'r' formats, the j parameter moves the origin of the text string toward the left by the specified numbers of columns. In the case of the 'r' format, the j parameter defines the right margin. Together with the 'c' format allows for optically correct centering of the string. A fragment of the code, using the writes subroutine is presented below.
Listing 4. Writing Text to the Screen
------------------------------------------------
call cursor('i')
j=0
1 call cls
call writes('r',10,j,'n','Hello')
call tab
call writes('r',11,j,'c','Hello')
call tab
call writes('r',12,j,'r','Hello')
call tab
call setcur(22,0)
write(*,*)'Read j Press E to Exit'
read(*,*,err=9)j
goto 1
9 call cursor('h')
end
------------------------------------------------
Source code for the writes subroutine is listed as
Listing 4. Source Code for the Writes Subroutine
------------------------------------------------
subroutine writes(x,i,jj,f,s)
character *(*)s,x*1,f*1
k=len(s)
j=jj
if(f.eq.'r')then
j=79-k-jj
elseif(f.eq.'c')then
j=40-k/2-jj
end if
call scribe(x,i,j,k,s)
end
;T H E S C R I B E S U B R O U T I N E
;call scribe(a,i,j,k,'Message$') ;a typical Fortran call
public scribe
stack segment stack 'stack'
db 64 dup (?)
stack ends
code segment
assume cs:code,ss:stack
scribe proc far
;Save base
push bp
mov bp,sp
;Save registers
push ax
push bx
push cx
push dx
push si
push di
;Get the arguments
mov si,[bp+6] ;message
push si ;save message address
mov di,[bp+14] ;column
mov bx,[bp+18] ;row
mov si,[bp+22] ;attribute
;Define cursor
mov dh,[bx] ;row
mov dl,[di] ;column
;Set cursor
mov ah,2
mov bh,0 ;page
int 10h
;Decode the attribute
mov cl,[si] ;get attribute
and cl,11011111b ;convert to uppercase
xor bx,bx ;zero bx register
cmp cl,'R'
je reverse
cmp cl,'H'
je intensity
cmp cl,'B'
je blink
cmp cl,'U'
je underline
cmp cl,'I'
je invisible
cmp cl,'A'
je attention
jmp normal
reverse label near
mov bl,01110000b
jmp go
intensity label near
mov bl,00001111b
jmp go
blink label near
mov bl,10000111b
jmp go
underline label near
mov bl,00000001b
jmp go
invisible label near
mov bl,00000000b
jmp go
attention label near
mov bl,10001111b
jmp go
normal label near
mov bl,00000111b
;Output the string
go label near
mov si,[bp+10] ;message length
mov cx,[si] ;length
pop si ;restore message address
write label near
lodsb
push cx
xor cx,cx
mov cx,1 ;write only one character
mov ah,9 ;BIOS 9/10h write
int 10h
inc dl ;increment cursor
mov ah,2 ;reset cursor
int 10h
pop cx
loop write
;Return
pop di
pop si
pop dx
pop cx
pop bx
pop ax
pop bp
ret 20
scribe endp
code ends
end
------------------------------------------------
FILL STRING WITH BLANK SPACES
The BLANK subroutine fills a string with blank spaces. It has a single parameter; i.e., the string to be filled with blanks. The string is variable dimensioned. An example of the subroutine call is
character string*30
call blank(string)
A variation of the blank subroutine is the subroutine PRIME. The prime subroutine fills a string with ASCII characters 255. This character does not echo and on the screen behaves as a blank. The subroutine has two parameters. The first parameter is the length of a string to be filled with ASCII 255; the second parameter is the string itself. The string is variable dimensioned. An example of the subroutine call is
character string*30
call prime(23,string)
The subroutine facilitates work in situations when it is helpful to differentiate between 'soft' and 'hard' blank space.
FORMAT STRINGS
The string formatting subroutines L_ADJUST, CENTER, and R_ADJUST left adjust text within a string, center text, and right adjust text, respectively. This group of subroutines is complemented by the L_PACK and R_PACK subroutines. While the l_adjust and r_adjust subroutines shift text toward their respective margins, the l_pack and r_pack subroutines shift text toward left or right by the indicated number of spaces. Also, the negative arguments are permitted for the l_pack and r_pack subroutines. In this case these subroutines function as shift right/left statements where the shifted characters overflowing the length of the string are discarded. These subroutines are called as
call l_adjust(margin,string)
call center(s)
call r_adjust(string,margin)
call l_pack(ishift,string)
call r_pack(string,ishift)
The strings in all above subroutines are variable dimensioned. A program fragments, illustrating the use of the string formatting subroutines follow.
Listing 4. Formatting the Text Strings
------------------------------------------------
character string*20
1 read(*,*)string
read(*,*)margin
call l_adjust(margin,string)
write(*,'(1x,a)')'----+----+----+----+'
write(*,'(1x,a)')string
write(*,*)length(string)
goto 1
end
-------------------------
character string*20
1 read(*,*)string
read(*,*)margin
call r_adjust(string,margin)
write(*,'(1x,a)')'----+-- --+----+----+'
write(*,'(1x,a)')string
write(*,*)length(string)
goto 1
end
------------------------------------------------
character a*10
a='eldorado'
write(*,*)a
call center(a)
write(*,*)a
write(*,*)'----------'
end
------------------------------------------------
character xdrive*80,p_drive*64
1 call cls
write(*,*)'Read string'
read(*,*)p_drive
write(*,*)'Read margin'
read(*,*)margin
xdrive=p_drive//'.NAM'
call l_pack(margin,xdrive)
write(*,'(1x,a)')'----+----+----+----+'
write(*,'(1x,a)')xdrive
write(*,*)length(xdrive)
call tab
goto 1
end
------------------------------------------------
character string*20
1 read(*,*)string
read(*,*)margin
call r_pack(string,margin)
write(*,'(1x,a)')'----+----+----+----+'
write(*,'(1x,a)')string
write(*,*)length(string)
goto 1
end
------------------------------------------------
MEASURE LENGTH OF TEXT WITHIN STRING
The LENGTH function is similar, but not equivalent to the Fortran intrinsic fu