Inside COBOL #13
Shawn M. Gordon
President S.M.Gordon & Associates
This month I am going to give you a practical application that you can use for all sorts of nifty little things. I am sure you have all used the program PSCREEN at one point or another to print the contents of the screen on a printer. This is one of the first utilities you usually run into. Well have you thought about what you could do with that same methodology? You could capture the screen contents to a disc file, create a very basic full screen editor, build a screen designer that you could read and interpret and then generate code, put a screen capture option in your fatal abort routines. The possibilities are really endless, the problem is of course, how to do it.
There is a really simple, fairly dependable way to read a screen in COBOL using just the COBOL verbs DISPLAY, and ACCEPT. I am however going to show you the harder more dependable way using Intrinsics.
First let’s talk a little bit about what PSCREEN does. The first thing it does is display a PSCREEN ‘banner’ at the current cursor position. This gives PSCREEN a ‘key’ to look for so it knows when to stop reading the screen. This methodology works very well, however I am going to show you a different way to do it. The next thing PSCREEN does is send the cursor ‘home’, this is the start of terminal memory, next PSCREEN reads each line off of the screen, and writes it to a spool file untill it sees it’s banner line. It then closes the spool file and terminates.
I am also going to use some more esoteric terminal escape sequences to retrieve the cursor position and to also position the cursor on the screen. I will describe them as I go. So here is the code to produce your own PSCREEN type program that will read the screen into a fixed MPE flat file.
01 STDIN PIC S9(4) COMP VALUE 0. 01 P PIC S9(4) COMP VALUE 0. 01 IN-BUFF PIC X(80) VALUE SPACES. 01 HOME-N-CLEAR. 03 PIC X VALUE %33. 03 PIC X VALUE 'h'. 03 PIC X VALUE %33. 03 PIC X VALUE 'J'. * 01 ABSOLUTE-CURSOR. 03 PIC X VALUE %33. 03 PIC X VALUE 'a'. * 01 READ-LINE. 03 PIC X VALUE %33. 03 PIC X VALUE 'd'. * 01 ABSOLUTE-ADDRESS. 03 PIC X(7) VALUE SPACES. 03 RA-ROW PIC 9(3) VALUE ZEROES. 03 PIC X. * 01 HOLD-ADDRESS. 03 PIC X VALUE %33. 03 PIC X(02) VALUE '&a'. 03 HA-COL PIC 9(3) VALUE ZEROES. 03 PIC X VALUE 'c'. 03 HA-ROW PIC 9(3) VALUE ZEROES. 03 PIC X VALUE 'R'. PROCEDURE DIVISION. * Retrieve the current cursor position from the screen CALL INTRINSIC "PRINT" USING ABSOLUTE-CURSOR, -2, %320 CALL INTRINSIC "READX" USING ABSOLUTE-ADDRESS, -11 CALL INTRINSIC 'FOPEN' USING \\, %244 GIVING STDIN. IF CC <> 0 DISPLAY 'FAILED TO FOPEN STDIN - ABORTING' STOP RUN. CALL INTRINSIC 'FCONTROL' USING STDIN, 13, P. DISPLAY HOME-N-CLEAR NO ADVANCING. PERFORM RA-ROW TIMES MOVE SPACES TO IN-BUFF DISPLAY HOLD-ADDRESS NO ADVANCING CALL INTRINSIC "PRINT" USING READ-LINE, -2, %320 CALL INTRINSIC "READX" USING IN-BUFF, -80 CALL INTRINSIC "FWRITE" USING MP-FNUM, IN-BUFF, -80, 0 IF CC <> 0 CALL INTRINSIC 'FCHECK' USING MP-FNUM, ERR CALL INTRINSIC 'FERRMSG' USING ERR, OUT-BUFF, ERR-LEN DISPLAY OUT-BUFF END-IF ADD 1 TO HA-ROW END-PERFORM. CALL INTRINSIC 'FCONTROL' USING STDIN, 12, P. CALL INTRINSIC 'FCLOSE' USING STDIN, 0, 0. STOP RUN.
Ok, let’s take it a line at a time. The first thing I am doing is calling the PRINT intrinsic, passing it the variable ABSOLUT-CURSOR. What this does is issue a response back to the terminal that you can read that is the absolute position of the cursor in the escape sequence for positioning the cursor. This explains why the next line calls the READX intrinsic to return an 11 byte field. If you look at your escape sequence manual, you will see that cursor positioning escape codes are 11 characters long. We now know what row and column this process was started at on the screen. One trick here is to issue an escape F to send the cursor to the end of terminal memory first, this is good if you know you are always going to want all of terminal memory, but not so good if you want to let the user control where they want to start the process.
Next we want to FOPEN the terminal for STDIN, the only reason I do this is so I can call the FCONTROL intrinsic to disable ECHO from the terminal. This reduces a lot of the overhead involved in reading the screen this way, it also makes it more reliable.
We are now going to loop the number of times that we retrieved from our ROW value when returning the cursor position. We then display the escape sequence that positions the cursor to a specific row and column. This allows us to absolutely control how the cursor moves from line to line by incrementing the HA-ROW value at the bottom of the PERFORM loop.
Now we actually get to the meat of how to get data off of the screen and do something with it. The READ-LINE variable contains an escape sequence that causes the current cursor line to be read and echoed back to the terminal. The READX intrinsic will pick up what is echoed and put it in our IN-BUFF variable. I now FWRITE it out to a file (I didn’t put the FOPEN of the file in, or the FCLOSE. You will have to do that yourself). Now we just keep looping until our PERFORM condition is satisfied.
Now as I said earlier, you could use DISPLAY and ACCEPT instead of PRINT and READX. You could also FOPEN stdlist and then use FREAD and FWRITE for the escape sequences, it all depends on how low level you want to get.
I’ll tell you another trick that I use this sort of methodology for. Say you want to keep free form documentation in a data base, like comments on a customer or something. What I do is make a data set with a key value, an 80 byte buffer, and a numeric field that is sequentially assigned. Now you can just read your screen, load your data set buffer, increment your sequence id number, and do a DBPUT. This means that any time they make changes you will want to delete the whole chain, and then re-DBPUT it. Obviously this is going to beat the hell out of the data set, but if the users need the functionality, you can always reorder the chains once a week.
I am trying to leave a little of the work up to you in some of these examples, because it is more important to understand why this stuff works than just being able to do it.
Well unless I get some input from you loyal readers, I am for the time being out of ideas for this column. Don’t worry about thinking your idea or question may be dumb, because there are no dumb questions, just dumb answers. I really do enjoy hearing from everyone with any bit of data. And the best part of course is having your name in print for all the world to see.