The publisher of the London Star (a sort of National Enquirer for scone-eaters) has taken a survey indicating that the three most attention-getting words for the British audience are (in order) "sex," "win," and "free." The chap said that if he could run a contest with a banner reading, "Win free sex!" he'd be the only newspaper left in London. After twenty years of trying to avoid seeing the tabloids down at the Piggly Wiggly checkout counter, I'd have to say the three equivalent words to American eyes are "murder," "UFOs," and "Elvis." (Which makes one long to run a story with the headline, "UFOs murder Elvis!" but alas, it didn't happen that way.)
Given that real programmers keep their pants on and listen to Boz Scaggs, what would you imagine are the three most attention-getting words on the covers of computer magazines? My bet would fall to "windows," "speed," and "graphics." ("Speed windows graphics!" is a cry Microsoft has been ignoring for years. ...)
These three words make for strange bedfellows, given that:
I have often grimaced, seeing magazines present code for windowing systems targeted at a 25-line or 350-pixel screen. This is the wrong way to be moving, folks. We should be modeling full typewritten pages on our screens, not postage stamps.
So, if you've been wondering what I've been leading up to in the last two columns, it's this: I'm building a sort of anti-windowing system. Rather than specifying a rectangular subset of the screen as a window, I'll be specifying the hardware display as a window into a he-man's 66-line virtual screen. This will have to do us until the rest of you guys catch on, and all buy 66-line text screens. (The Micro Display Systems' Genius VHR has been out there for four years, and you can buy one from 47th Street Computer in Nuh Yawk for $999.)
A virtual screen is virtual for the same reason that virtual memory is virtual: The hardware display doesn't have enough screen to show a full page, so you have to put part of the page elsewhere and bring pieces of the full page onto the display as needed.
Our "elsewhere" is the heap, and the whole thing is held together with pointers in a highly memory-efficient fashion. (Turbo Pascal is every bit as clever with pointers as C is. Maybe more.) Figure 1 is a schematic of the memory architecture of a virtual screen. Follow along on the figure during the following discussion.
What should a virtual screen allow us to do? The key is this: We are modeling a piece of paper. Ideally, we should be able to write text to any location on the virtual screen without worrying about whether any given portion of the screen is currently displayed or not. Independently, we should be able to pan the visible window into the virtual screen at will, by one line or any other number of lines at a time.
The important thing is that we abandon the visible display as a reference frame. Line 1 is the first line of the virtual screen, whether visible or not. The visible display becomes a window that we slide up and down the virtual screen, inspecting it at will.
Right off, this forces us to abandon the PC's hardware cursor, which is a prisoner of the current visible display. Abandoning the hardware cursor is probably a good idea. The PC's cursor management firmware is bug ridden and quirky, and I'd just as soon turn the sorry thing off and leave it off. (Turning it off is easy. Turning it back on again is not-but those are tales for another time.)
Physically, the virtual screen is a collection of blocks of memory on the heap, with one block allocated for each line on the virtual screen. The virtual screen is held together with a descriptor record having the structure shown in Listing One, page 142.
Here, HEIGHT is a constant that specifies the number of lines (counting from 1) in the virtual screen. I use 66, modeling our typical letter-size paper sheet at the traditional six lines of type per inch. You can, however, change HEIGHT to some other value to model legal-size paper (84 lines) or one of the European paper sizes. For now, don't probe the internal structure of individual lines (I'll get to that later), but simply think of them as storage arrays for text.
For simplicity's sake, I have designed the virtual screen system so that the size of the screens is fixed at compile time, and only one size of screen (that specified by the constants HEIGHT and WIDTH) is supported at once. If fields were added to the record screen to describe the screen's dimensions, the size of a screen could be specified at screen initialization time; that is, the time when a screen is created on the heap with the InitScreen procedure. Some complication would have to be added to the supporting procedures and functions, but it's not impossible or even especially difficult, and might be worth it to be able to support the modeling of both letter and legal paper in the same application at the same time.
The heart of the virtual screen record is a pair of arrays of pointers, ShowPtrs and StorePtrs. The differences between their purposes are subtle but important: StorePtrs points to where the virtual screen information is stored. The pointers in StorePtrs are initialized at screen initialization time and never change until the screen is disposed of. ShowPtrs, by contrast, contains pointers that are used to direct display output; that is, where the information is to be shown, hence the name. Some of ShowPtrs, pointers point into the display adapter's visible refresh buffer. The rest point to lines in the virtual screen on the heap. Which pointers point where is the key to this whole virtual business.
When the virtual screen is initialized with InitScreen, the first Visible Y pointers are set to point into successive 160-byte regions in the display adapter refresh buffer, starting at the address given in TextBufferOrigin. (Both VisibleY and TextBufferOrigin are initialized and exported by the TextInfo unit I described last month. VisibleY contains the number of lines currently displayed on the screen, and TextBufferOrigin is a pointer pointing to the first byte of the display adapter refresh buffer.)
The field TopLine in the descriptor record stores the number of the virtual screen line shown at the top of the visible display. In Figure 1, this is line 7; note that ShowPtrs[7] is the first pointer in the array that points into the physical display refresh buffer, rather than into the virtual screen itself, Starting with the index stored in TopLine, a number of pointers corresponding to the number of visible lines (VisibleY) point into the visible refresh buffer.
Writing to the screen is easy. We don't have to know whether the line to which we wish to write is currently visible. If we want to write to line 8, we specify line 8 with a custom GotoXY procedure, and then the WriteTo procedure will send the output to whatever line is pointed to by ShowPtrs[8]. If ShowPtrs[8] points to a line on the visible display, we see the output immediately; if ShowPtrs[8]instead points to a line in the virtual screen, we won't see the output and will have to pan the visible display until the newly written line comes into view.
Sliding the visible display up and down as a "window" into the virtual screen is by far the trickiest part of SCREENS.PAS (Listing Two, page 142). Regardless of which direction the pan operation takes, the work is all done within procedure pan. Understanding pan is best done by looking to Figure 1 during the following discussion.
Accomplishing a pan involves a number of separate tasks, each of which must be accomplished in a specific order. It's possible to perform both an upward and a downward pan using the same run of code, but the algorithm is more straightforward by testing for the direction of the pan and using a separate sequence of statements for each of the two directions. The following sequence is for a downward pan; that is, sliding the visible window such that first line 1 is at the top, then line 2, then line 3, and so on.
I can hear the screams already: That sliding a window up and down all the time is a monstrous violation of good ergonomic software design. And that's true ... but the key is the phrase "all the time." Simply having virtual screens does not require that you use them in all cases. I might also add that you can use them well or you can use them badly, just as with a paring knife or false eyelashes.
A little cleverness can help a lot. A 66-line form might be designed in three major portions: The top 25 lines as one portion, the next 18 lines as the second section, and the remaining 23 lines as the third section. Why? A person with a 25-line screen gets the top section at a glance, pans down to pick up the middle 18 lines, and then pans again to get the final 23 lines. A person with an EGA or VGA can read the top two sections (totalling 43 lines) at a glance, with one pan down to see the last 23 lines.
A person with a Genius tube, of course, gets the whole meatball in one glance and doesn't need to pan at all. If you have enough fields on a form to fill 66 lines, you're going to have to manage multiple forms for 25, 43, and 50 line screens anyway. Designing a form in this fashion manages multiple forms for you without any change in the software.
And then, once the form is completed in the virtual screen, you can print the virtual screen to paper as a unit --again, without any additional fussing to combine multiple forms onto one sheet for printing.
Virtual screens also make report previewing easy and convenient. Reports must be formatted for paper, not for the screen, so if the user wants to preview a printed report on the screen, he or she must be able to pan around the sheet in order to inspect the whole sheet on a sub-66-line screen. My infamous Little Black Book program JBOOK.PAS (which I will someday release as shareware) prints address information on Day Runner-style memo book sheets, many of which accept over 50 lines in the Laserjet II's 16.66 pitch Line Printer font. Using SCREENS.PAS, JBOOK builds each sheet of the paper report in a virtual screen, and then prints directly from the virtual screen.
Listing Two is the Turbo Pascal code for the virtual display management unit. Listing Three, page 144, is a short demo program, SCRNTEST.PAS. SCRNTEST allows you to load the first 66 lines of any text file into a virtual screen and then pan through it. Pressing Del will clear the center line of the visible display, which can then be scrolled onto and off of the screen as proof that the screen really is virtual. (SCRNTEST cannot write to disk, so your text file will not be corrupted.)
SCREENS.PAS is complete and usable as it stands, but much could be added to it. I'll be presenting additional procedures for the unit in future columns. These will include a box-drawing procedure, as well as code to allow the creation of "ghost" screens stored entirely on the heap, and flashed in and out of view with a little external assembly language magic. Also, from time to time I'll present screen-oriented procedures showing how different user interface concepts are implemented, and they'll be built on the virtual screens platform shown here.
And if I can score the loan of a video board that displays more than 80 columns horizontally, I'll take a crack at adding horizontal panning to the system. People who have display boards that support 132 column text, write to me. Let me know what's good.
I've learned most of the information on text displays presented in these last few columns the hard way, but more and more of it is being gathered up into reference books specifically devoted to video topics.
Let me call attention to two recent titles that I've found very useful. Programmer's Guide to PC and PS/2 Video Systems by Richard Wilton is the most broad ranging, in that it covers the full spectrum of PC video, including the CGA, MDA, Hercules, and something called the InColor card that I've never heard of. Programmer's Guide to the EGA and VGA Cards by Richard F. Ferraro is more focused, but covers its territory in greater depth. The books distinguish themselves quite clearly in several ways. The Wilton book is a software book, that contains tremendous quantities of C and assembler code, including what looks like a fast graphics BITBLT in assembler. Sadly, the source is printed in an eye-crossing pale green that suggests an intention to present source code in 3-D, only they forgot to bind the little glasses into the book. (Microsoft Press recognizes the error and will print it more clearly in future press runs.) The Ferraro book is a hardware book, and tells you much more about what goes on at a register level. Wilton presents his great quantities of data in reference manual fashion, and doesn't provide as much in the line of example code.
The two dovetail nicely with one another, and I consider both of them essential. I'll let Kent pass on how useful they are to the graphics programmer, but they have not failed so far to answer any questions I've had on text video.
I seem to be making a habit of finishing up these columns on major holidays. (In the Santa Cruz area, Halloween is major.) A little later on this drippy Christmas Eve, Carol and I will be packing off in the Magic Van to have a quiet dinner with friends on the slopes of Loma Prieta.
A good night, I think, to shut the machines down and look for the best that is human somewhere else. Give somebody who counts a hug, and it'll be right there.
Promise.
Programmer's Guide to the PC and PS/2 Video Systems by Richard Wilton Microsoft Press, 1987 ISBN 1-55615-103-9 531 pp. $24.95 Source code disk $21.95
Programmer's Guide to the EGA and VGA Cards by Richard F. Ferraro Addison-Wesley, 1988 ISBN 0-201-12692-3 606 pp. $26.95 Source code disk $24.99
Genius VHR Video Subsystems Micro Display Systems 1310 Vermillion Str. Hastings, MN 55033 612-437-2233 Board and monitor $1,395
A bug has been discoverd in the listings for the March 1989 "Structured Programming" column. On line 175 of TEXTINFO.PAS, (within procedure GetFont Size) replace the line
with the line
The bug apparently affects VGA display boards only, and then only under certain circumstances. Please update your copy of TEXTINFO.PAS accordingly.
_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann
[LISTING ONE]
Copyright © 1989, Dr. Dobb's Journal
Anatomy of a V Screen
Panning the Screen
By the Numbers
1. Make sure that the pan will not take the visible display beyond the array bounds of the virtual screen. To do so would trigger a runtime range error if range error trapping is enabled. If range error trapping is not enabled, the program will behave erratically and probably abort to DOS. If a multiple-line pan would take the visible display out of range, but some number of lines remain between the limit of the visible display and the end of the virtual screen, the number of lines in the pan (By Lines) is adjusted so that the pan "just makes it."
2. Copy the newly hidden line or lines from the visible display to the virtual screen. Remember that I/O to a virtual screen may be sent to either the refresh buffer or to the virtual screen lines allocated on the heap. A visible line may contain data that does not exist in the visible line's virtual counterpart on the heap. Therefore, each time a visible line or lines "roll off" the top or bottom of the visible screen, those lines must be copied into their corresponding virtual lines.
3. Glitch the portion of ShowPtrs that points into the visible refresh buffer down by the number of lines in the pan. If the top line being displayed is 7 (as shown in Figure 1) the series of pointers beginning at ShowPtrs[7] must be moved down the array by 4 bytes (the size of a Turbo Pascal pointer) so that what was ShowPtrs[7] becomes ShowPtrs[8] and so on. The number of pointers involved depends on the number of lines in the visible display, which is given by global variable VisibleY. The glitch is done with a simple call to Turbo Pascal's Move statement.
4. Repoint ShowPtrs' pointers to the newly-hidden lines back into the virtual screen. Step 3 copied the data in those lines from the visible refresh buffer into their corresponding lines on the heap. Here we must now repoint the pointers that used to point into the visible refresh buffer back into the heap. This is done simply by assigning the corresponding pointers from StorePtrs (which always point onto the heap) into their twins in ShowPtrs.
5. Glitch the visible display upward by the number of lines in the pan. It may seem counterintuitive at first, but to slide the window downward you must glitch the display upward. Think of it this way: When you pass a cow on the Interstate, you're moving forward, but the cow appears to move backward. As Einstein would say, Everything's relative. The glitch itself is done by a call to the firmware scrolling routines in ROM BIOS video interrupt 10H.
6. Copy the newly-visible line or lines from the virtual screen on the heap into the visible refresh buffer. Glitching the display leaves a blank line or lines, which must be filled with lines from the virtual screen. This is the mirror-image to step 2, and is done quickly with Turbo Pascal's Move procedure.
7. Finally, update the TopLine counter in the virtual screen's descriptor record. The top line shown in the visible display has changed, and TopLine must always reflect the number of that top line.
To Virtualize or Not to Virtualize
Off the Shelf Video Information
Carol of the BELs
Products Mentioned
Errata
BL:= 0;
BH:= 0;
Screen = RECORD
ShowPtrs : ARRAY[1..HEIGHT] OF LinePtr;
StorePtrs : ARRAY[1..HEIGHT] OF LinePtr;
X,Y : Byte;
TopLine : 1..HEIGHT;
FollowCursor : Boolean
END;
[LISTING TWO]
{--------------------------------------------------------------}
{ SCREENS }
{ Virtual screen management unit }
{ }
{ by Jeff Duntemann KI6RA }
{ Turbo Pascal 5.0 }
{ Last modified 12/24/88 }
{--------------------------------------------------------------}
UNIT Screens;
INTERFACE
USES DOS, { Standard Borland unit }
TextInfo; { Given last issue; DDJ 3/89 }
CONST
WIDTH = 80; { These are the character sizes of the virtual screens }
HEIGHT = 66; { KEEP IN MIND THAT THIS IS A 1-ORIGIN SYSTEM!!!!!!!!! }
{ I.e., we count rows and columns from *1*, not 0. }
UP = True; { Constants for glitching and panning }
DOWN = False;
TYPE
String5 = STRING[5];
String10 = STRING[10];
String80 = STRING[80];
{ Lines are made of these; helps us mix characters and attributes: }
ScreenAtom = RECORD
CASE Boolean OF
True : (Ch : Char;
Attr : Byte);
False : (Atom : Word);
END;
LinePtr = ^Line;
Line = ARRAY[1..WIDTH] OF ScreenAtom;
ScreenPtr = ^Screen;
Screen = RECORD
ShowPtrs : ARRAY[1..HEIGHT] OF LinePtr;
StorePtrs : ARRAY[1..HEIGHT] OF LinePtr;
X,Y : Byte;
TopLine : 1..HEIGHT;
FollowCursor : Boolean
END;
CONST
ClearAtom : ScreenAtom = (Ch : ' '; { ASCII space char }
Attr : $07); { "Normal" screen attribute }
VAR
CurrentAttr : Byte; { Exported global, *not* a function! }
PROCEDURE ClearLine(LineTarget : LinePtr;
VisibleX : Byte;
ClearAtom : ScreenAtom);
INLINE
($58/ { POP AX } { Pop filler char/attribute into AX }
$59/ { POP CX } { Pop line length (repeat count) into CX }
$5F/ { POP ES } { Pop line address segment into ES }
$07/ { POP DI } { Pop line address offset into DI }
$8C/$C2/ { MOV DX,ES } { Move ES into DX for test against 0 }
$81/$FA/0/0/ { CMP DX,0000 } { Compare ES value (in DX) against 0 }
$74/$02/ { JE 2 } { If Equal, jump ahead 2 bytes }
$F3/$AB); { REP STOSW } { Otherwise, blast that line to atoms! }
FUNCTION BooStr(BooleanValue : Boolean) : String5;
PROCEDURE ClrScreen(Target : ScreenPtr; ClearAtom : ScreenAtom);
PROCEDURE DisposeOfScreen(VAR Target : ScreenPtr);
PROCEDURE GotoXY(Target : ScreenPtr; NewX,NewY : Byte);
PROCEDURE InitScreen(Target : ScreenPtr; Visible : Boolean);
FUNCTION IntStr(IntegerValue,FieldWidth : Integer) : String10;
PROCEDURE Pan(Target : ScreenPtr; PanUp : Boolean; ByLines : Integer);
FUNCTION RealStr(RealValue : Real; Exponential : Boolean;
FieldWidth,DecimalWidth : Integer) : String80;
PROCEDURE WriteTo(Target : ScreenPtr; S : String);
PROCEDURE WritelnTo(Target : ScreenPtr; S : String);
IMPLEMENTATION
{ Private to SCREENS--make it public if you need it. }
PROCEDURE GlitchDisplay(Up : Boolean; ByLines : Integer);
VAR
Service : Byte;
Regs : Registers;
BEGIN
IF Up THEN Service := $06 ELSE Service := $07;
WITH Regs DO
BEGIN
AH := Service;
AL := ByLines;
BH := CurrentAttr; { Attribute for blanked line(s) }
CH := 0; { CX & DX: Glitch the full display }
CL := 0;
DH := VisibleY-1;
DL := VisibleX-1;
END;
Intr($10,Regs);
END;
{ Returns string equivalent of RealValue: }
FUNCTION RealStr(RealValue : Real; Exponential : Boolean;
FieldWidth,DecimalWidth : Integer) : String80;
VAR
Dummy : String80;
BEGIN
IF Exponential THEN
Str(RealValue : FieldWidth,Dummy)
ELSE
Str(RealValue : FieldWidth : DecimalWidth,Dummy);
RealStr := Dummy
END;
{ Returns string equivalent of BooleanValue: }
FUNCTION BooStr(BooleanValue : Boolean) : String5;
BEGIN
IF BooleanValue THEN BooStr := 'TRUE'
ELSE BooStr := 'FALSE'
END;
{ Returns string equivalent of IntegerValue: }
FUNCTION IntStr(IntegerValue,FieldWidth : Integer) : String10;
VAR
Dummy : String10;
BEGIN
Str(IntegerValue : FieldWidth,Dummy);
IntStr := Dummy
END;
{ Clears Target to the atom passed in ClearAtom: }
PROCEDURE ClrScreen(Target : ScreenPtr; ClearAtom : ScreenAtom);
VAR
I : Integer;
BEGIN
WITH Target^ DO
BEGIN
{ Brute force: Clear all lines at the ends of pointer }
{ referents, even though non-visible lines are cleared twice }
FOR I := 1 TO HEIGHT DO
ClearLine(ShowPtrs[I],VisibleX,ClearAtom);
FOR I := 1 TO HEIGHT DO
ClearLine(StorePtrs[I],VisibleX,ClearAtom);
X := 1; Y := 1;
END
END;
{ Moves logical (*not* hardware!) cursor to NewX,NewY: }
PROCEDURE GotoXY(Target : ScreenPtr; NewX,NewY : Byte);
{ Simply places new values in descriptor record's X & Y fields }
BEGIN
WITH Target^ DO
BEGIN
X := NewX;
Y := NewY
END
END;
{ V-Screen equivalent of Write: }
PROCEDURE WriteTo(Target : ScreenPtr; S : String);
VAR
I,K : Integer;
TX : Byte;
ShiftedAttr : Word;
BEGIN
{ Put attribute in the high byte of a word: }
ShiftedAttr := CurrentAttr SHL 8;
WITH Target^ DO
BEGIN
TX := X;
K := 0;
FOR I := 0 TO Length(S)-1 DO
BEGIN
IF X+I > VisibleX THEN { If string goes past end of line: }
BEGIN
Inc(Y); { Increment Y value }
X := 1; TX := 1; { Reset X and temp X value to 1 }
K := 0; { K is the line-offset counter }
END;
{ Here we combine the character from the string and the }
{ current attribute via OR, and assign it to its location }
{ on the screen: }
Word(ShowPtrs[Y]^[X+K]) := Word(S[I+1]) OR ShiftedAttr;
Inc(TX); Inc(K);
END;
X := TX; { Update X value in descriptor record }
END
END;
{ V-Screen equivalent of Writeln: }
PROCEDURE WritelnTo(Target : ScreenPtr; S : String);
BEGIN
WriteTo(Target,S);
Inc(Target^.Y); { These 2 lines are the equivalent of CR/LF }
Target^.X := 1
END;
{ Moves the visible display as a window onto a full-page virtual screen: }
PROCEDURE Pan(Target : ScreenPtr; PanUp : Boolean; ByLines : Integer);
VAR
I : Integer;
YOffset : byte;
BEGIN
YOffset := VisibleY-1; { Compensates for 1-based line numbering }
WITH Target^ DO
IF PanUp THEN { If we want to pan the display up the screen }
BEGIN
{ Don't do anything if we're at the top of the V-screen: }
IF TopLine > 1 THEN
BEGIN
{ If we're not at the top but ByLines would take us out of }
{ legal range, adjust ByLines to scroll the rest of the way: }
IF TopLine - ByLines < 1 THEN ByLines := TopLine - 1;
{ Move newly-hidden lines into virtual screen buffer: }
FOR I := TopLine + YOffset DOWNTO
TopLine + YOffset - (ByLines-1) DO
Move(ShowPtrs[I]^,StorePtrs[I]^,VisibleX * 2);
{ Glitch the display pointer array up: }
Move(ShowPtrs[TopLine],ShowPtrs[TopLine-ByLines],VisibleY * 4);
{ Repoint affected line pointers into virtual screen: }
FOR I := TopLine + YOffset DOWNTO
TopLine + YOffset - (ByLines-1) DO
ShowPtrs[I] := StorePtrs[I];
{ Glitch the display buffer down: }
GlitchDisplay(False,ByLines);
{ Update virtual screen's TopLine counter: }
TopLine := TopLine - ByLines;
{ Move newly-visible lines to display from virtual screen: }
FOR I := TopLine TO TopLine + (ByLines-1) DO
Move(StorePtrs[I]^,ShowPtrs[I]^,VisibleX * 2);
END
END
ELSE { If we want to pan the display down the screen }
BEGIN
{ First check if the pan would take us out of legal line range: }
IF TopLine + YOffset < Height THEN
BEGIN
{ If we're not at bottom but ByLines would take us out of }
{ legal range, adjust ByLines to scroll the rest of the way: }
IF TopLine + YOffset + ByLines > HEIGHT THEN
ByLines := HEIGHT - (TopLine + YOffset);
{ Move newly-hidden lines into virtual screen buffer: }
FOR I := TopLine TO TopLine + (ByLines-1) DO
Move(ShowPtrs[I]^,StorePtrs[I]^,VisibleX * 2);
{ Glitch the display pointer array down: }
Move(ShowPtrs[TopLine],ShowPtrs[TopLine+ByLines],VisibleY * 4);
{ Repoint affected line pointers into virtual screen: }
FOR I := TopLine TO TopLine + (ByLines-1) DO
ShowPtrs[I] := StorePtrs[I];
{ Glitch the display buffer up }
GlitchDisplay(True,ByLines);
{ Move newly-visible lines to display from virtual screen: }
FOR I := TopLine + VisibleY TO TopLine + VisibleY + (ByLines-1) DO
Move(StorePtrs[I]^,ShowPtrs[I]^,VisibleX * 2);
{ And finally, update virtual screen's TopLine counter: }
TopLine := TopLine + ByLines
END
END
END;
{ You *must* init a V-Screen through this proc before using it: }
PROCEDURE InitScreen(Target : ScreenPtr; Visible : Boolean);
VAR
I : Integer;
BEGIN
WITH Target^ DO
BEGIN
FOR I := 1 TO HEIGHT DO
BEGIN
New(ShowPtrs[I]); { Allocate a line on the heap }
StorePtrs[I] := ShowPtrs[I] { Duplicate pointer }
END;
X := 1;
Y := 1;
TopLine := 1;
FollowCursor := True;
IF Visible THEN { As opposed to a "ghost" screen on the heap }
FOR I := 0 TO VisibleY-1 DO
ShowPtrs[I+1] := { Repoint pointers into refresh buffer }
Ptr(Seg(TextBufferOrigin^),
Ofs(TextBufferOrigin^) + (I * (VisibleX * 2)))
END
END;
{ Frees up heapspace occupied by Target. DON'T use if Target is the }
{ address of a statically declared-record obtained with @ or Addr()!! }
PROCEDURE DisposeOfScreen(VAR Target : ScreenPtr);
VAR
I : Integer;
BEGIN
FOR I := 1 TO Height DO Dispose(Target^.ShowPtrs[I]);
Dispose(Target);
Target := NIL
END;
{ SCREENS Initialization Section: }
BEGIN
CurrentAttr := $07; { $07 is the "normal" video attribute }
END.
[LISTING THREE]
{--------------------------------------------------------------}
{ SCREENTEST }
{ Virtual screen demo program }
{ }
{ by Jeff Duntemann KI6RA }
{ Turbo Pascal 5.0 }
{ Last modified 12/24/88 }
{--------------------------------------------------------------}
PROGRAM ScreenTest;
USES DOS, { Standard Borland unit }
TextInfo, { Given last issue; DDJ 3/89 }
Screens; { Given this issue; DDJ 4/89 }
CONST
PanBy = 1; { Specifies # of lines to pan at once }
VAR
I : Integer;
Check : Integer;
Ch : Char;
Extended : Boolean;
Scancode : Byte;
Shifts : Byte;
TestScreen : Screen;
MyScreen : ScreenPtr;
FileName : String80;
TestFile : Text;
HalftoneAtom : ScreenAtom;
InString : String80;
{->>>>GetKey<<<<-----------------------------------------------}
{ }
{ Filename: GETKEY.SRC -- Last modified 7/23/88 }
{ }
{ This routine uses ROM BIOS services to test for the presence }
{ of a character waiting in the keyboard buffer and, if one is }
{ waiting, return it. The function itself returns a TRUE }
{ if a character has been read. The character is returned in }
{ Ch. If the key pressed was a "special" (non-ASCII) key, the }
{ Boolean variable Extended will be set to TRUE and the scan }
{ code of the special key will be returned in Scan. In }
{ addition, GETKEY returns shift status each time it is called }
{ regardless of whether or not a character was read. Shift }
{ status is returned as eight flag bits in byte Shifts, }
{ according to the bitmap below: }
{ }
{ BITS }
{ 7 6 5 4 3 2 1 0 }
{ 1 . . . . . . . INSERT (1=Active) }
{ . 1 . . . . . . CAPS LOCK (1=Active) }
{ . . 1 . . . . . NUM LOCK (1=Active) }
{ . . . 1 . . . . SCROLL LOCK (1=Active) }
{ . . . . 1 . . . ALT (1=Depressed) }
{ . . . . . 1 . . CTRL (1=Depressed) }
{ . . . . . . 1 . LEFT SHIFT (1=Depressed) }
{ . . . . . . . 1 RIGHT SHIFT (1=Depressed) }
{ }
{ Test for individual bits using masks and the AND operator: }
{ }
{ IF (Shifts AND $0A) = $0A THEN CtrlAndAltArePressed; }
{ }
{ From: COMPLETE TURBO PASCAL 5.0 by Jeff Duntemann }
{ Scott, Foresman & Co., Inc. 1988 ISBN 0-673-38355-5 }
{--------------------------------------------------------------}
FUNCTION GetKey(VAR Ch : Char;
VAR Extended : Boolean;
VAR Scan : Byte;
Var Shifts : Byte) : Boolean;
VAR Regs : Registers;
Ready : Boolean;
BEGIN
Extended := False; Scan := 0;
Regs.AH := $01; { AH=1: Check for keystroke }
Intr($16,Regs); { Interrupt $16: Keyboard services}
Ready := (Regs.Flags AND $40) = 0;
IF Ready THEN
BEGIN
Regs.AH := 0; { Char is ready; go read it... }
Intr($16,Regs); { ...using AH = 0: Read Char }
Ch := Chr(Regs.AL); { The char is returned in AL }
Scan := Regs.AH; { ...and scan code in AH. }
IF Ch = Chr(0) THEN Extended := True ELSE Extended := False;
END;
Regs.AH := $02; { AH=2: Get shift/alt/ctrl status }
Intr($16,Regs);
Shifts := Regs.AL;
GetKey := Ready
END;
BEGIN
IF ParamCount < 1 THEN { No file-ee, no work-ee }
BEGIN
Writeln('>>>SCRNTEST by Jeff Duntemann ');
Writeln(' Virtual screen demo program');
Writeln(' Version of 12/24/88 -- Turbo Pascal 5.0');
Writeln(' Invoke: SCRNTEST <textfile> <CR>');
Writeln(' Use up/down arrows to pan window;');
Writeln(' the DEL key to blank out a line.');
Writeln(' Press "Q" or "q" to quit...');
END
ELSE
BEGIN
FileName := ParamStr(1); { See if named file can be opened }
Assign(TestFile,FileName);
{$I-} Reset(TestFile); {$I+}
Check := IOResult;
IF Check <> 0 THEN { If not, complain: }
BEGIN
Writeln('>>Test file ',FileName,' Cannot be opened.');
Writeln(' Please invoke again with a valid file name.');
END
ELSE
BEGIN { File can be opened; let's read it into a V-screen }
HalftoneAtom.Ch := Chr(177); HalftoneAtom.Attr := $07;
MyScreen := @TestScreen;
InitScreen(MyScreen,True); { Allocate & init the screen }
ClrScreen(MyScreen,ClearAtom); { Clear the screen }
IF NOT EOF(TestFile) THEN { If the file isn't empty... }
BEGIN
I := 1; { Start from line 1 }
WHILE (NOT EOF(TestFile)) AND (I <= HEIGHT) DO
BEGIN
Readln(TestFile,InString);
{ Truncate each line at 70 columns: }
InString := Copy(InString,1,70);
{ Write line number to the V-Screen: }
WriteTo(MyScreen,IntStr(I,5));
{ Write the data line to the V-Screen: }
WritelnTo(MyScreen,': '+InString);
Inc(I) { Increment the line counter }
END;
{ Up to 66 lines of the file are on the screen. }
{ Here we pan up on the up arrow, and down on }
{ the down arrow. 'Q' quits the program. }
Extended := False;
REPEAT
IF Extended THEN
CASE Scancode OF
{ DEL } 83 : WITH MyScreen^ DO
ClearLine(ShowPtrs[TopLine + (VisibleY DIV 2)],
VisibleX,HalftoneAtom);
{ Up Arrow } 72 : Pan(MyScreen,Up,PanBy);
{ Down arrow } 80 : Pan(MyScreen,Down,PanBy);
END; { CASE }
REPEAT UNTIL GetKey(Ch,Extended,Scancode,Shifts);
UNTIL Ch IN ['Q','q'];
END
END
END
END.