Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Structured Programming


AUG90: STRUCTURED PROGRAMMING

You See Toothpaste Pumps; I See Coil Forms ...

Lord knows, there shouldn't be places named "Drug Emporium" (what sort of message does that give our kids?) but I confess I was in there the other day, looking for some Poore's Potato Chips and the latest Computer Shopper. It's definitely the category-killer store of its kind in Scottsdale, with whole aisles devoted to things that might get five shelf-feet in Safeway. I trucked down the toothpaste aisle, oblivious until I hit the toothpaste pump section. Something stopped me cold -- bear with me on this; I'll explain -- in that all those toothpaste pumps sitting side-by-side on the shelves looked just like plug-in shortwave coil forms.

Sure. Hey, if you're over forty you might remember: Shortwave radios used to use these little things with plugs on the bottom and wire on them, that plugged into a hole in the back/front/ side/whatever of the shortwave radio. Ham radio operators used them until fairly recently, and some of us incorrigible atavists use them until this very day. They always came in groups of four or five, one for each shortwave band, hence the feeling of deja vu in seeing 17 pumps of Ultra Brite side by side, standing on end as we always stood the coils on end, flared side down, ready to grab if we got tired of the BBC and wanted to corrupt our brains with Radio Moscow.

I haven't built many radios lately (staying alive has been challenge enough) but I bought a pump of Ultra Brite anyway, and in another six weeks, once the stuff inside is gone, I'm gonna ram an old octal tube base up its business end, wrap some #22 enameled wire around it, build a 1-FET regen and go sniffing for the "Voice of America."

It's a talent I have. I see extraordinary uses for ordinary things. You see scrap plywood; I see a potential birdhouse. You see toothpaste pumps; I see coil forms. I have a garage full of ordinary things awaiting their extraordinary applications. (Some have waited for many years. Ask Carol about it; she'll have plenty to say....)

Bingo!

I've used this talent often in programming, where the difference between the ordinary and the extraordinary is lots fuzzier, and the material itself almost infinitely mutable. I guess you could say that everything in programming is a potential birdhouse; if you need a birdhouse badly enough you'll figure out a way.

Last week I was facing a data entry problem I hadn't faced before. I had to design a means to enter and store magazine bingo cards. You know what I mean; those Free Information! cards in the backs of virtually all magazines (including DDJ) where you circle an advertiser's number and they magically send you their full-color brochure. Magazines get hundreds or (God help us) thousands of these in the mail all the time. Each card must be entered into a database somehow, starting with the reader name and address but ending by somehow encoding every number the reader has seen fit to circle.

How to encode a circled number was an interesting question. I started out by taking the low road and creating a 255-character text field in Paradox, into which each circled number would be typed, separated by spaces. Paradox has good pattern-matching abilities in its query form, and it worked tolerably well once you swallowed the data entry. That is, it worked -- until some joker mailed us a card with the whole block of 200 numbers circled.

That drove home the fact that 255 characters would not contain much more than 60 or so numbers. Half a second's thought should have told me that the bingo numbers weren't numbers anyway. They were Booleans, either True (circled) or False (not circled.) And our bingo card was just an array of 200 Booleans. The birdhouse I had to build would be an array of 200 Booleans with some means of screen display and editing. I took a look to see what I had lying around in the line of potential birdhouses, and it hit me: A Pascal set is (with a little reworking) an array of 256 Boolean values. Packed, too -- in 32 bytes, with not a bit of wasted space. Bingo!

Get set

Apart from SET OF Char (which I use incessantly for text filtering) I haven't done much with sets of more than five or six elements. But consider:

  TYPE    BitSet = SET OF 0..255;

This definition gives us a bitmapped data item containing 256 flag bits, with machinery built right into Pascal for adding, removing, and testing for presence and absence of individual bits. These two statements amount to pretty much the same thing:

   MyBooleanArray[17]:=True;
   MyBitSet := MyBitSet + 17;

The + symbol here is acting as set union, not addition. (We have overloaded operators in Pascal too! Kind of like discovering that you've been speaking in prose all your life....) In the second statement, you're assigning to MyBitSet the union of the earlier state of MyBitSet and the value 17. Think of 17 as an enumeration constant here, and it should make sense.

Similarly, you can remove an item from a set by using the set difference operator, - , like this:

  MyBitSet := MyBitSet - 17;

Again, this may look odd unless you remind yourself that 17 is not acting arithmetically here. Call 17 by its Martian name, Foobity-Foo (see my book, Assembly Language From Square One before calling out the guys with the butterfly nets) if you have trouble divorcing it from the realm of practical math.

The bottom line: As the central data item in my model of the bingo card, I would use a Pascal set of the cardinal numbers from 0 to 255. Each number would be either in the set (circled) or not in the set (left alone.)

Breaking Your Own Rules

As is my wont these days, I immediately forged an object around the BitSet data item. In doing so I broke one of my own personal rules that I shared with you in a previous column: Don't make an object out of anything special-cased by the compiler. This would include most primitive data types including Booleans, characters, strings, and -- most emphatically -- sets.

The advice I gave before was and is still sound. Nonetheless, I consider the exception to be good practice because I'm not really using the set as sets are generally used. In effect, I'm using a set as a packed array of Booleans, so just this once, I'll build an object around a Pascal primitive data type and not feel guilty about it.

At the highest level, designing an object consists of asking the question: What must the object know how to do? For my application, it wasn't a long list. The object must be able to clear itself (that is, set all bits to 0) display itself, and edit itself. That's all.

The resulting object is implemented in Listing One (page 172), SETOBJ.PAS.

A Set Object

The visual expression of the set data consists of 16 lines of sixteen three-digit numbers, starting with 000 and going to 255. When a numbered bit in the set is set (for example, binary 1) the three digits are displayed in reverse video. For all bits that are cleared (binary 0) the three-digit numbers are displayed in normal white-on-black video.

Within the SetObject.Edit method, a text cursor is created by poking the two triangle symbols (ASCII 16 and 17) to the screen on either side of one of the 256 numbers indicating bits in the set. I call the bit so indicated the "hot bit." When you press Enter, the state of the hot bit is toggled from cleared (the default) to set and back. By bouncing the cursor around the matrix using the arrow keys and pressing Enter where appropriate, you can set or clear any bit in the matrix at random, with minimal keystroking effort. Finally, pressing Esc ends the edit session, with any changes stored in the SetData field of SetObject.

You can try out SetObject's different methods by running Listing Two (page 177), SETTEST.PAS.

Pokey Video

Only two aspects of the code bear serious explaining: The way the matrix pattern is generated on the screen, and the mouse support for editing.

By sheer bad luck I was forced off of my ramcharged 25-MHz 386 and onto a gen-u-wine o-riginal 4.77-MHz IBM PC while developing SetObject. At first I drew the matrix on the screen using a pair of nested loops containing GotoXY and Write, as most reasonable people would. This worked fairly well on the 386. However, on the PC the results came close to making me scream: Zzzzzzzzzzzzzzzzzzzit! I could watch the matrix flow into place from the top of the screen down. Any screen that I can watch drawing itself is too damned slow; watching mainframe screens slog their way to wholeness was considered entertainment rivaling old Andy Griffith reruns back when I worked at Xerox MIS/DP.

Sorry Andy. When such things happen, I go right to assembler and a whole new approach. Instead of drawing the matrix every time I need it, I create the matrix as a memory image on the heap, and flash it in with the Move block move statement any time I want to display it.

Move is absurdly simple and just as fast; the trickiness is all in getting the matrix onto the heap to begin with. Using Move to transfer an easily-readable character array such as MatrixText in Listing One won't work, because a screen memory image must have an attribute byte after each and every byte to be displayed. So I wrote MatrixBlast, a specialized Move variant that moves a character array onto the heap while inserting an attribute byte after each character byte. It's an INLINE macro and pretty close to as fast as such a creature can be, at least until Michael Abrash gets hold of it. The set matrix I'm using here is only 16 x 80, but you can easily use MatrixBlast to move entire 25 x 80 screens from a text array (perhaps in the form of a typed constant) to the heap, and then flash the screen into view with Move.

Once moved onto the heap, the raw matrix image must be updated with highlight attributes to reflect the current state of the 255 bits in SetData field of SetObject. There's a second INLINE macro that does that job: AttributeBlast, which is called from a tight loop inside the Show method. AttributeBlast moves a specified attribute byte into the memory image on the heap, without disturbing the visible screen data already there. In a sense, AttributeBlast machine guns attribute bytes in between the ASCII data bytes in the image on the heap, starting at some offset from the pointer to the image, and continuing for a specified number of characters.

With MatrixBlast and AttributeBlast, there were no longer any pokey screen problems, even on the old PC.

A note on a bug that drove me bonkers: While writing MatrixBlast, I inadvertently entered an ordinary right parenthesis instead of a right curly bracket at the end of the first comment line:

  {Pop attribute character into AX)

The net effect was to comment out the $5B opcode (POP BX) on the next line, which got the stack frame all confused and blew my system away every time. It wasn't until I started tracing the program opcode-by-opcode with Turbo Debugger that the missing $5B became apparent, and even then it took some definite right-brain thinking. (We're not good at looking for something that isn't there!) Be careful when working with INLINE. The compiler lets you do what you like -- and one small twitch in a bracket (or lack of one) can send your system to Bim-Bom-Bay.

Mouse Control

I'm not fond of mouse input except when it makes sense -- and mouse input for intense text data entry and editing rarely makes sense, regardless of what the Mac crazies think. I type at about 110 words a minute, and if I had to break stride to grab the mouse to slide up a line or two to fix a pair of transposed letters, I'd never get anything finished on time.

Picking and toggling bits on the matrix, however, is a perfect job for the mouse, because everything is point-and-shoot. There's no entry of characters involved; you click on a number and that bit changes state. For this reason, I built mouse control into SetObject's Edit method. Furthermore, the user doesn't need to specify mouse control; if the mouse driver is available, SetObject will detect it and enable the mouse cursor. If the mouse driver isn't found, the arrow keys can be used to move the hot bit and the Enter key to change the hot bit's state.

In the SetObj unit's initialization section, a call is made to a function that looks for either a null or an IRET instruction (opcode $CF) and, finding neither, assumes that there is a mouse driver at the other end of interrupt vector 51. I've always thought that a little dicey, but no better way has been provided by Microsoft, so we'll run with it. A global Boolean named MouseAvailable is set to True if the unit assumes a mouse driver is installed. All through the unit, MouseAvailable is tested whenever something associated with the mouse needs to be done. If no driver is installed, the unit works perfectly well from the keyboard. If the mouse is there, the driver is reset and the mouse cursor turned on.

For Want of a Flag

There is a shortcoming in the Microsoft standard mouse driver that has infuriated me since I wrote my first mouseaware program back in 1983. (I still use that original, first-run Microsoft Mouse, lint-catcher mechanical gimcrackery and all.) Whereas it's possible to turn the mouse cursor on and off at will, there's no way to determine if the mouse cursor is already on. You have to keep your own flag or make assumptions. This is dumb -- there's a documented but unavailable cursor-visible flag inside the driver somewhere, and all it would take would be a little code to return it during the status function call.

I do make certain assumptions here, in that if the mouse driver has been found, the mouse cursor is assumed to be visible during the execution of the Edit method. This is only significant during execution of the Show method, which must turn off the visible mouse cursor in order to blast the matrix image from the heap into the video refresh buffer. (If you write over the mouse cursor, odd things happen.) At the end of Show, the mouse cursor is made visible again -- if Show was called from within Edit. You don't want the mouse cursor coming on if you're just calling Show to display a set object's data with no intention to edit, as is done in Listing Two. Hence the otherwise worthless EditInProcess flag that must be carted around by all instances of SetObj. If we could just test to see if the mouse cursor were visible at any given time, we could just not turn it back on, if it wasn't on already when we entered Show. Grrrrr.

Another note on mouse control: While the mouse is visible, it is limited to travelling only within the extent of the 16 lines of the matrix, courtesy of a call to mouse function 8 just before the cursor is turned on at the top of SetObject. Edit. This is important; for simplicity I don't check to see if the mouse is outside the matrix, and if you let the mouse roam the screen it will still toggle the state of bits in the matrix even when outside the matrix. (Comment out the call to function 8 and you'll see what I mean.)

I suppose that only a few of you have magazines and their attendant bingo cards to service, but SetObj may have some other uses. Test scores? Survey results? Be creative. Consider SetObj a potential birdhouse, and keep your mind open to uses that don't follow from your traditional notions of what sets are for. Hey, do like I do: If it looks like a coil form -- go looking for the wire!

Products Mentioned

  Microsoft Mouse Programmer's Reference
  (no author given)
  Microsoft Press, 1989
  ISBN 1-55615-191-8
  Softcover, 321 pages, $29.95
  Listings disk is bound into all copies

  Modula-2 A Complete Guide
  by K. N. King
  D. C. Heath and Company, 1988
  1-800-334-3284
  ISBN 0-669-11091-4
  Softcover, 656 pages, $36
  Listings disk available from author,
  Price: $10

  TopSpeed Modula-2 V2.0
  Jensen & Partners International
  1101 San Antonio Road, Suite 301
  Mountain View, CA 94043
  415-967-3200
  DOS Price: $199
  OS/2 Price: $495

The Last Word on Mice

Microsoft has finally published the definitive book on mouse programming, six years after it should have. Better late than never, I guess -- and I'm kicking myself for not having written it back in 1985, when I had the notion. Anyway -- if you have things with tails on your desktop, pick it up: Microsoft Mouse Programmer's Reference. (The book was written in-house and carries no author's byline.)

The book is short-ish, but it's clear, and accurate, and tolerably easy to read. There are lots of code examples, mostly in C, but the explanations are strong enough to carry you into any language that has a software interrupt primitive or allows assembly language externals. The book is expensive as thin books go, but it includes a disk with the listings and some mouse menu folderol the usefulness of which escapes me.

For the completeness of its coverage, a must-have.

More Modula Books

I've gotten a handful of new books on Modula-2 since complaining of their dearth a few columns ago. None seem much worth mentioning except one: Modula-2 A Complete Guide, by K.N. King. The book is by no means new (1988) but as it's a text and not a trade volume, I had no way to know of its publication before Dr. King was kind enough to send it to me.

Modula-2 A Complete Guide is by far the best book on Modula now on my shelves. It's big (650+ pages), clear, complete, and honest -- it says what I discovered years ago, that Niklaus Wirth's suggested Processes module is next to worthless. Furthermore, King proposes a replacement that's considerably better, though I haven't had a chance to try it yet.

The writing rises much above the stuffy incompetence I see in far too many CS textbooks these days. Keep in mind that Modula-2 A Complete Guide is not a DOS book, so you won't get lots of interesting tidbits on accessing the joystick port or tweaking display adapter registers. (Good thing, too -- perhaps Dr. King has left enough for me to make a book of.) You won't find it in ordinary bookstores, although a college bookstore where the book is taught would have it. Fortunately, it can be ordered directly from the publisher, through the 800 number given in the Products Mentioned box.

Go through this book and you'll have standard Modula-2 in your hip pocket. Highly recommended.

Closing In on Modula Objects

By next column I hope to have JPI's new 2.00 Version of TopSpeed Modula-2, which includes object-oriented extensions similar to those in Turbo Pascal 5.5. The siren song of objects has taken me away from Modula to a great extent this past year, and now it's time to go back, I think.

There may be some griping, especially from the portability paranoids, but hey, guys, consider: Every one of the languages I currently use is now object-oriented. Pascal, Modula-2, Smalltalk, Actor, C (no, no kidding!) so . . . what's left? The only major holdout is Basic, which I haven't touched for awhile, but there are intriguing rumblings from Microsoft that they may take Big Bill's Baby in an objective direction soon.

And then there are those ongoing rumors of object-oriented Cobol. . . .

Stranger things have happened. The Berlin Wall is gone. Democrats have started voting against tax hikes. They still grow broccoli.

In other words, don't bet the rent.

_STRUCTURED PROGRAMMING COLUMN_ by Jeff Duntemann

[LISTING ONE]

<a name="01c4_0010">

{-------------------------------------------------}
{                     SETOBJ                      }
{  Set object with an interactive editing method  }
{                        by Jeff Duntemann        }
{                        For DDJ 8/90             }
{                        Turbo Pascal 5.5         }
{                        Last modified 5/4/90     }
{-------------------------------------------------}

UNIT SetObj;

INTERFACE

USES DOS,Crt;

TYPE
  BitSet    = SET OF 0..255;  { Maximum size generic set }

  SetObject =
    OBJECT
      SetData   : BitSet;     { The set data itself }
      HotBit    : Integer;    { Bit currently subject to editing }
      ShowAtRow : Integer;    { Matrix may appear at row 1 to 8 }
      MatrixPtr : Pointer;    { Points to matrix pattern on heap }
      Origin    : Integer;    { Display text starts at 0 or 1 }
      Attribute : Integer;    { Attribute for nonhighlighted elements }
      Highlight : Integer;    { Attribute for highlighted elements }
      EditInProcess : Boolean;    { True if inside the Edit method }
      CONSTRUCTOR Init(InitialOrigin,
                       InitialAttribute,
                       InitialHighlight,
                       InitialStartRow : Integer);
      DESTRUCTOR  Done;       { Removes object from memory }
      PROCEDURE   ClearSet;   { Forces all set bits to 0 }
      PROCEDURE   Show;       { Displays set data; doesn't edit }
      PROCEDURE   Edit;       { Displays and edits set data }
    END;

IMPLEMENTATION

TYPE
  Char40 = ARRAY[0..39] OF CHAR;  { For the matrix; see below }

CONST
  LeftCursorChar  = #16;   { These are the bracketing characters   }
  RightCursorChar = #17;   { Indicating which set element is being }
                           { edited. }

  { This is the text portion of the 16-line number matrix used to display }
  { and edit set elements.  They are first stored onto the heap, then the }
  { object's attribute is merged with the text on the heap.  This way,    }
  { you can move the whole image onto the screen, attributes and all,     }
  { with a single Move statement. }

  MatrixText : ARRAY[0..32] OF Char40 = (
           ' 000  001  002  003  004  005  006  007 ',
           ' 008  009  010  011  012  013  014  015 ',
           ' 016  017  018  019  020  021  022  023 ',
           ' 024  025  026  027  028  029  030  031 ',
           ' 032  033  034  035  036  037  038  039 ',
           ' 040  041  042  043  044  045  046  047 ',
           ' 048  049  050  051  052  053  054  055 ',
           ' 056  057  058  059  060  061  062  063 ',
           ' 064  065  066  067  068  069  070  071 ',
           ' 072  073  074  075  076  077  078  079 ',
           ' 080  081  082  083  084  085  086  087 ',
           ' 088  089  090  091  092  093  094  095 ',
           ' 096  097  098  099  100  101  102  103 ',
           ' 104  105  106  107  108  109  110  111 ',
           ' 112  113  114  115  116  117  118  119 ',
           ' 120  121  122  123  124  125  126  127 ',
           ' 128  129  130  131  132  133  134  135 ',
           ' 136  137  138  139  140  141  142  143 ',
           ' 144  145  146  147  148  149  150  151 ',
           ' 152  153  154  155  156  157  158  159 ',
           ' 160  161  162  163  164  165  166  167 ',
           ' 168  169  170  171  172  173  174  175 ',
           ' 176  177  178  179  180  181  182  183 ',
           ' 184  185  186  187  188  189  190  191 ',
           ' 192  193  194  195  196  197  198  199 ',
           ' 200  201  202  203  204  205  206  207 ',
           ' 208  209  210  211  212  213  214  215 ',
           ' 216  217  218  219  220  221  222  223 ',
           ' 224  225  226  227  228  229  230  231 ',
           ' 232  233  224  235  236  237  238  239 ',
           ' 240  241  242  243  244  245  246  247 ',
           ' 248  249  250  251  252  253  254  255 ',
           ' 256                                    ');

VAR
  VidBufferPtr   : Pointer;  { Global, set in the init. section }
  MouseAvailable : Boolean;  { Global, set in the init. section }

{-------------------------------------------------}
{  Procedures and functions private to this unit: }
{-------------------------------------------------}

{ This is the general-purpose mouse call primitive: }

PROCEDURE MouseCall(VAR M1,M2,M3,M4 : Word);

VAR
  Regs : Registers;

BEGIN
  WITH Regs DO
    BEGIN
      AX := M1; BX := M2; CX := M3; DX := M4;
    END;
  INTR(51,Regs);  { 51 = $33 = Mouse driver interrupt vector }
  WITH Regs DO
    BEGIN
      M1 := AX; M2 := BX; M3 := CX; M4 := DX;
    END;
END;

PROCEDURE ShowMouse;

VAR
  M1,M2,M3,M4 : Word;

BEGIN
  M1 := 1; MouseCall(M1,M2,M3,M4);
END;

PROCEDURE HideMouse;

VAR
  M1,M2,M3,M4 : Word;

BEGIN
  M1 := 2; MouseCall(M1,M2,M3,M4);
END;

{ If called when left mouse button is down, waits for release }

PROCEDURE WaitForMouseRelease;

VAR
  M1,ButtonStatus,M3,M4 : Word;

BEGIN
  M1 := 3;
  REPEAT
    MouseCall(M1,ButtonStatus,M3,M4);
  UNTIL NOT Odd(ButtonStatus);  { Wait until Bit 0 goes to 0 }
END;

PROCEDURE UhUh;  { Says "uh-uh" when you press the wrong key }

VAR
  I : Integer;

BEGIN
  FOR I := 1 TO 2 DO
    BEGIN
      Sound(50); Delay(100); NoSound; Delay(50);
    END;
END;

FUNCTION MouseIsInstalled : Boolean;

TYPE
  BytePtr = ^Byte;

VAR
  TestVector : BytePtr;

BEGIN
  GetIntVec(51,Pointer(TestVector));
  { $CF is the binary opcode for the IRET instruction; }
  { in many BIOSes, the startup code puts IRETs into   }
  { most unused bectors. NIL, of course, is 4 zeroes.  }
  IF (TestVector = NIL) OR (TestVector^ = $CF) THEN
    MouseIsInstalled := False
  ELSE
    MouseIsInstalled := True
END;

{ Returns True if running on a mono system: }

FUNCTION IsMono : Boolean;

VAR
  Regs : Registers;

BEGIN
  Intr(17,Regs);
  IF (Regs.AX AND $0030) = $30 THEN IsMono := True
    ELSE IsMono := False;
END;

{-------------------------------------------------------}
{ Returns True if left mouse button was clicked, and if }
{ the button *was* clicked, returns the X,Y position    }
{ of the mouse at click-time in MouseX,MouseY.  If      }
{ called when the mouse was *not* clicked, returns 0    }
{ in MouseX and MouseY.                                 }
{-------------------------------------------------------}

FUNCTION MouseWasClicked(VAR MouseX,MouseY : Word) : Boolean;

VAR
  M1,ButtonStatus : Word;

BEGIN
  M1 := 3; MouseCall(M1,ButtonStatus,MouseX,MouseY);
  IF Odd(ButtonStatus) THEN MouseWasClicked := True
    ELSE
      BEGIN
        MouseWasClicked := False;
        MouseX := 0;
        MouseY := 0;
      END
END;

PROCEDURE MatrixBlast(TextPtr,Heapptr  : Pointer;
                      SizeOfMatrix     : Word;
                      Origin,Attribute : Byte);

INLINE
($58/     { POP AX }     { Pop attribute character into AX}
 $5B/     { POP BX }     { Pop origin digit into BX }
 $59/     { POP CX }     { Pop byte count into CX }
 $5F/     { POP DI }     { Pop heap pointer offset portion into DI }
 $07/     { POP ES }     { Pop heap pointer segment portion into ES }
 $5E/     { POP SI }     { Pop matrix pointer offset portion into SI }
 $5A/     { POP DX }     { Pop matrix pointer segment portion into DX }
 $1E/     { PUSH DS }    { Store Turbo's DS value on the stack }
 $8E/$DA/ { MOV DS,DX }  { Move DX into DS }
 $86/$C4/ { XCHG AL,AH } { Get attribute into hi byte of AX }
 $03/$F3/ { ADD SI,BX }  { Add origin adj. to matrix pointer offset }
 $AC/     { LODSB }      { Load MatrixText character at DS:SI into AL }
 $AB/     { STOSW }      { Store matrix char/attr pair in AX to ES:DI }
 $E2/$FC/ { LOOP -4 }    { Loop back to LOADSB until CX = 0 }
 $1F);    { POP DS }     { Pop Turbo's DS value from stack back to DS }

 PROCEDURE AttributeBlast(ImagePtr : Pointer;
                          ImageOffset,Attribute,WordCount : Integer);

 INLINE(
 $59/     { POP CX }     { Pop word count into CX }
 $58/     { POP AX }     { Pop attribute value into AX }
 $5B/     { POP BX }     { Pop image offset value into BX }
 $D1/$E3/ { SHL BX,1 }   { Multiply image offset by 2, for words not bytes }
 $5F/     { POP DI }     { Pop offset portion of image pointer into DI }
 $07/     { POP ES }     { Pop segment portion of image pointer into ES }
 $03/$FB/ { ADD DI,BX }  { Add image offset value to pointer offset }
 $47/     { INC DI }     { Add 1 to DI to point to attribute of 1st char }
 $AA/     { STOSB }      { Store AL to ES:DI; INC DI by 1 }
 $47/     { INC DI }     { Increment DI past character byte }
 $E2/$FC);{ LOOP -4 }    { Loop back to STOSB until CX = 0 }

{------------------------------------}
{  Method definitions for SetObject: }
{------------------------------------}

CONSTRUCTOR SetObject.Init(InitialOrigin,
                           InitialAttribute,
                           InitialHighlight,
                           InitialStartRow : Integer);

BEGIN
  { Set initial values for state variables: }
  Origin    := InitialOrigin;
  Attribute := InitialAttribute;
  Highlight := InitialHighlight;
  ShowAtRow := InitialStartRow;
  SetData   := [];        { Set initial set value to empty }
  HotBit    := 0;         { Set initial hot bit to 0 }
  EditInProcess := False; { Not in Edit method right now! }

  GetMem(MatrixPtr,2560); { Allocate space on the heap for the matrix }
  { Blast the matrix pattern, with attributes, onto the heap: }
  MatrixBlast(@MatrixText,MatrixPtr,
              SizeOf(MatrixText),(Origin*5),Attribute);
END;

DESTRUCTOR SetObject.Done;

BEGIN
  { Free the memory occupied by the matrix image: }
  FreeMem(MatrixPtr,2560);
END;

PROCEDURE SetObject.ClearSet;

BEGIN
  FillChar(SetData,Sizeof(SetData),Chr(0));
END;

PROCEDURE SetObject.Show;

VAR
  I,Offset : Integer;
  ShowPtr  : Pointer;

BEGIN
  { It's important not to clobber the visible mouse cursor in the }
  { video refresh buffer.  This is why we turn it off for the     }
  { duration of this procedure: }
  IF MouseAvailable THEN IF EditInProcess THEN HideMouse;
  FOR I := 0 TO 255 DO
    IF I IN SetData THEN
      AttributeBlast(MatrixPtr,(I*5)+1,Highlight,3)
    ELSE
      AttributeBlast(MatrixPtr,(I*5)+1,Attribute,3);
  Offset := (ShowAtRow-1) * 160;  { Offset in bytes into the vid. buffer }
  { Create a pointer to the matrix location in the video buffer: }
  ShowPtr := Pointer(LongInt(VidBufferPtr) + Offset);
  { Move the matrix image from the heap into the video buffer: }
  Move(MatrixPtr^,ShowPtr^,(Sizeof(MatrixText) SHL 1)-79);
  { If the mouse is available we assume we're using it: }
  IF MouseAvailable THEN IF EditInProcess THEN ShowMouse;
END;

{--------------------------------------------------------------------}
{ This is the beef of the SetObject concept: A method that brings up }
{ a 16 X 16 matrix of bit numbers, each of which corrresponds to one }
{ bit in the set.  The method allows the user to zero in on a single }
{ bit through the keyboard or through the mouse if the driver is     }
{ loaded. Click on the number (or press Enter) and the bit changes   }
{ state, as indicated by screen highlighting.  This is useful for    }
{ debugging or even data entry to a set object.                      }
{--------------------------------------------------------------------}

PROCEDURE SetObject.Edit;

VAR
  I             : Integer;
  M1,M2,M3,M4   : Word;
  MouseX,MouseY : Word;
  Quit          : Boolean;
  InCh          : Char;

PROCEDURE PokeToCursor(Left,Right : Char);

BEGIN
  Char(Pointer(LongInt(MatrixPtr)+(HotBit*10))^)   := Left;
  Char(Pointer(LongInt(MatrixPtr)+(HotBit*10)+8)^) := Right;
END;

PROCEDURE MoveHotBitTo(NewHotBit : Integer);

BEGIN
  PokeToCursor(' ',' ');
  HotBit := NewHotBit;
  PokeToCursor(LeftCursorChar,RightCursorChar);
  Show;
END;

{ Converts a mouse screen X,Y to a bit position in the matrix }
{ from 0-255: }

FUNCTION MouseBitPosition(MouseX,MouseY : Integer) : Integer;

VAR
  ScreenX,ScreenY : Word;

BEGIN
  ScreenX := (MouseX DIV 8) + 1; ScreenY := (MouseY DIV 8) + 1;
  ScreenY := ScreenY - ShowAtRow;  { Adjust Y for screen position }
  MouseBitPosition := (ScreenY * 16) + (ScreenX DIV 5);
END;

{ Simply toggles the set bit specified in FlipBitNumber: }

PROCEDURE ToggleBit(FlipBitNumber : Integer);

BEGIN
  IF FlipBitNumber IN SetData THEN  { If it's a 1-bit }
    BEGIN
      SetData := SetData - [FlipBitNumber];
      AttributeBlast(MatrixPtr,(FlipBitNumber*5)+1,Attribute,3);
    END
  ELSE   { If it's a 0-bit }
    SetData := SetData + [FlipBitNumber];
END;

BEGIN  { Body of Edit }
  EditInProcess := True;
  { Make keyboard cursor visible at HotBit: }
  PokeToCursor(LeftCursorChar,RightCursorChar);
  Show;

  { Turn on mouse cursor if mouse is available: }
  IF MouseAvailable THEN
    BEGIN
      M1 := 0; MouseCall(M1,M2,M3,M4); { Reset mouse }
      M1 := 8; M3 := ((ShowAtRow-1) SHL 3);
      M4 := ((ShowAtRow-1) SHL 3) + 120;
      MouseCall(M1,M2,M3,M4);  { Limit mouse movement vertically }
      M1 := 1; MouseCall(M1,M2,M3,M4); { Show mouse cursor }
    END;

  Quit := False;
  REPEAT
    IF MouseAvailable THEN     { Test global Boolean variable }
      IF MouseWasClicked(MouseX,MouseY) THEN
        BEGIN                            { Mouse was clicked... }
          I := MouseBitPosition(MouseX,MouseY); {..on what bit? }
          MoveHotBitTo(I);           { Move hot bit to that bit }
          ToggleBit(I);       { Toggle the selected bit's state }
          WaitForMouseRelease;        { Wait for button release }
          Show;                          { Redisplay the matrix }
        END;

    IF KeyPressed THEN  { If the user pressed any key... }
      BEGIN
        InCh := ReadKey;         { Get the key }
        IF InCh = Chr(0) THEN    { If it was null... }
          BEGIN
            InCh := ReadKey;     { Get the second half }
            CASE Ord(InCh) OF    {  and parse it: }
  { Up }      72 : IF HotBit > 15 THEN I := HotBit-16 ELSE Uhuh;
  { Left }    75 : IF HotBit > 0 THEN I := Hotbit-1 ELSE Uhuh;
  { Right }   77 : IF HotBit < 255 THEN I := HotBit+1 ELSE Uhuh;
  { Down }    80 : IF HotBit < 239 THEN I := HotBit+16 ELSE Uhuh;
  { Home }    71 : I := 0;
  { PgUp }    73 : I := 15;
  { End }     79 : I := 240;
  { PgDn }    81 : I := 255;
              ELSE Uhuh;
            END; { CASE }
            MoveHotBitTo(I);
          END;
        CASE Ord(InCh) OF
          13 : ToggleBit(HotBit);    { Enter }
          27 : Quit := True;         { ESC }
          ELSE {Uhuh;}
        END; { CASE }
        Show;
      END;
  UNTIL Quit;
  IF MouseAvailable THEN HideMouse;     { Hide mouse cursor }
  PokeToCursor(' ',' ');  { Erase cursor framing characters }
  EditInProcess := False;
END;

{ Initialization section: }

BEGIN
  IF IsMono THEN VidBufferPtr := Ptr($B000,0)
    ELSE VidBufferPtr := Ptr($B800,0);
  { Here we look for the presence of the mouse driver: }
  MouseAvailable := MouseIsInstalled;
END.




<a name="01c4_0011"><a name="01c4_0011">
<a name="01c4_0012">
[LISTING TWO]
<a name="01c4_0012">

PROGRAM SetTest;

USES Crt,SetObj;  { SetObj presented in DDJ 8/90 }

VAR
  MySet : SetObject;

BEGIN
  TextBackground(Black);
  ClrScr;
  MySet.Init(0,$07,$70,1);               { Create the object }
  MySet.SetData := [0,17,42,121,93,250]; { Give set a value }
  MySet.Edit;                            { Edit the set }
  ClrScr;                                { Clear screen }
  Readln;                                { Wait for keypress }
  MySet.Show;                            { Show the set }
  MySet.ClearSet;                        { Zero the set }
  Readln;                                { Wait for keypress }
  MySet.Show;                            { Show the cleared set }
  Readln;                         { And wait for final keypress }
END.











Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.