Copy Link
Add to Bookmark
Report

QBNews Volume 2 Number 4

eZine's profile picture
Published in 
QBNews
 · 28 Dec 2019

     Volume  2, Number  4                                 December 22, 1991 













**************************************************
* *
* QBNews *
* *
* International QuickBASIC Electronic *
* Newsleter *
* *
* Dedicated to promoting QuickBASIC around *
* the world *
* *
**************************************************
















The QBNews is an electronic newsletter published by Clearware
Computing. It can be freely distributed providing NO CHARGE is charged
for distribution. The QBNews is copyrighted in full by Clearware
Computing. The authors hold the copyright to their individual
articles. All program code appearing in QBNews is released into the
public domain. You may do what you wish with the code except
copyright it. QBNews must be distributed whole and unmodified.

You can write The QBNews at:

The QBNews
P.O. Box 507
Sandy Hook, CT 06482

Copyright (c) 1991 by Clearware Computing.

The QBNews Page i
Volume 2, Number 4 December 22, 1991



----------------------------------------------------------------------

T A B L E O F C O N T E N T S


1. From the Editor's Desk
The QBNews Turns Three! ...................................... 1
Receiving The QBNews ......................................... 2
Submitting Articles to The QBNews ............................ 4

2. Advertisement
GFA-BASIC for Windows and DOS ................................ 5

3. The QBNews Professional Library
Popup Windows by Christy Gemmel .............................. 6

4. Beginner's Corner
Using COM 3 and COM 4 with QB by Dick Dennison ............... 31
The UEVENT Bug by Ray Crumrine ............................... 33

5. Fun and Games
Having a Ball by Charles Graham and David Cleary ............. 35































The QBNews Page ii
Volume 2, Number 4 December 22, 1991



----------------------------------------------------------------------
F r o m t h e E d i t o r ' s D e s k
----------------------------------------------------------------------

The QBNews Turns Three!

This issue marks the end of the second year for The QBNews. For the
most part, I am extremely satisfied with how Volume 2 has turned out.
I have some big plans for Volume 3, but I also have some concerns as
to the future of The QBNews.

First, we'll discuss Volume 3. For Volume 3, I plan to have each issue
focus primarily on one topic. This is something I have been wanting to
do, but when you rely on contributions of articles, you take what you
can get. Here are the topics I would like to cover in the upcoming
year, along with their release dates.

Volume 3 Number 1 - March 15, 1992

BASIC 7.x PDS
New keywords and improvements over QB 4.5
Using ISAM
Using Codeview and The Programmers Workbench
Converting ASM routines over to using Farstrings

Volume 3 Number 2 - June 14, 1992

Graphics Programming
Using BASIC's graphics routines
Saving and loading PCX files
Using Sprites
Using Fonts

Volume 3 Number 3 - September 13, 1992

Database
Topics to be determined

Volume 3 Number 4 - November 30, 1992

Communications
Topics to be determined

As you can see, 1992 will be a full year for The QBNews. However, that
brings us to my concerns. The level of interest in The QBNews has
seemed to drop off dramatically. I had to end the Ask The Doctor
section simply because I didn't receive one question. The number of
comments and suggestions I receive in the mail is almost nil. And I am
starting to run low on volunteers to write articles. It seems that at
the end of each year, I say how I may be discontinuing The QBNews. If
this disturbing trend keeps up, I will have no choice but to do just
that. Let's make 1992 a great year for The QBNews.


The QBNews Page 1
Volume 2, Number 4 December 22, 1991

Receiving The QBNews

The QBNews is distributed mainly through BBS systems around the
world. Some of the networks it gets distributed through are SDS
(Software Distribution System) and PDN (Programmers Distribution
Network). Ask the sysop of your local board about these networks to
see if there is a node in your area.

The QBNews can also be found on CompuServe in the MSLang
(Microsoft Language) forum. It can be found in file area 1 or 2 of
that forum. Just search for the keyword QBNEWS. The QBNews will also
be available on PC-Link. I send them to Steve Craver, who is the BASIC
Programming Forum Host on PC-LINK and he will make them available. I
would appreciate anybody who could upload The QBNews to other services
such as GENIE since I don't have access to these.

I have also set up a high speed distribution network for people
who would like to download The QBNews at 9600 baud. The following
boards allow first time callers download privileges also. They are:

Name Sysop Location Number Node #
---------------------------------------------------------------------

Treasure Island Don Dawson Danbury, CT 203-791-8532 1:141/730

Gulf Coast BBS Jim Brewer New PortRichey,FL 904-563-2547 1:365/12

221B Baker St. James Young Panama City,FL 904-871-6536 1:3608/1

EMC/80 Jim Harre St. Louis, MO 314-843-0001 1:100/555

Apple Capitol BBS Bob Finley Wenatchee, WA 509-663-3618 1:344/61


Finally, you can download The QBNews from these vendors BBS's:

The Crescent Software Support BBS 203-426-5958

The EllTech Support BBS 404-928-7111

The Microhelp BUG BBS 404-552-0567
404-594-9625


You do not have to be a customer of these vendors in order to download
The QBNews, but the Microhelp BBS only allows non-members 15 minutes
of time per call.

If you would like to receive The QBNews on disk, I offer a yearly
subscription for $20.00. This includes four disks containing each
issue as it is published. If you would like a disk with all the back
issues of The QBNews, please enclose an additional $5.00. The pricing
structure is as follows:


The QBNews Page 2
Volume 2, Number 4 December 22, 1991

Base Price for 1 Year - $20.00
Disk with Back Issues - $5.00
3.5" disk surcharge - $5.00
Canada and Mexico surcharge - $5.00
All other foreign orders - $10.00

The base price includes 5.25" 360k disks. Send a check or money
order in U.S. funds to:

The QBNews
P.O. Box 507
Sandy Hook, CT 06482

Please be sure to specify what archive format you want. The
QBNews normally uses PKZip as it's archiver.







































The QBNews Page 3
Volume 2, Number 4 December 22, 1991

Submitting Articles to The QBNews

The QBNews relies on it's readers to submit articles. If you are
interested in submitting an article, please send a disk of Ascii text
of no more than 70 characters per line to:

The QBNews
P.O. Box 507
Sandy Hook, CT 06482

Articles can also be submitted via E-Mail. Send them via Compuserve to
76510,1725 or via FidoNet to 1:141/777. I can be reached at the above
addresses as well as on Prodigy as HSRW18A.









































The QBNews Page 4
Volume 2, Number 4 December 22, 1991



----------------------------------------------------------------------
A d v e r t i s e m e n t
----------------------------------------------------------------------

GFA-BASIC GIVES YOU MORE PROGRAMMING POWER Now includes a
than both Visual Basic and Realizer dBASE III/IV
COMBINED! engine!

If your going to program for Windows, GFA-BASIC is the one tool that
can help you migrate your Qwick BASIC programs over. It's easy to use,
contains hundreds of commands and functions and is lightening fast.
After buying many libraries for Visual Basic and Realizer, one key
customer gave up and with GFA, implemented a networked Windows
application on 100 PC's in record time. The following comparison was
derived from that project: GFA Visual Realizer
BASIC Basic
Visual programming tools YES YES YES
Dynamic Modification of Window Spec YES NO YES
Capability to Run Multiple Parent Windows YES YES NO
Resolution Independent Dialog Boxes YES NO YES
Pre-defined File-Select Boxes YES NO YES
All Direct Windows API calls w/out defining YES NO NO
Built-In dBASE III/IV read/update/append YES NO NO
Built-In record locking for multi-user/LAN YES NO NO DLL
Easy internal multi-tasking YES NO YES
Advanced Graphics e.g., spline, stretch YES NO NO
bezier curve. ellipse, arcs
Dynamic BITMAPS--stretch, re-size YES NO NO
Binary File load/save YES NO YES
Total # Commands & Functions >700 <250 <300
Maximum array size 20MEG 64K 64K
Full set array Matrix commands YES NO NO
Multiple Dimension Arrays YES YES NO
User defined structures i.e., TYPES YES YES NO
Integer & floating point math YES YES FP ONLY
Advanced math functions(trig & statistics) YES NO SOME
Direct fast COM port access commands YES NO LIB
DLL size required with compiled .exe 150K 250K 400K
Typical Program Execution Speed FAST SLOW SLOW
Mouse/GUI applications portable to MS-DOS YES NO NO
Add'l libraries you need to buy NONE MANY SOME

Half Price Introductory Offer
List Intro
GFA-BASIC for Windows 3.0 $495 $295
GFA-BASIC for MS-DOS $295 $195
Both Windows and DOS version $790 $395*
*Includes newly released dBASE III/IV Engine for Dos & Windows
Call us with your order and we will GUARANTEE that you will be
satisfied with these products for one full year!

Call 1-800-766-6GFA GFA Software Technologies, Inc.
27 Congress St., Salem, MA 01970

The QBNews Page 5
Volume 2, Number 4 December 22, 1991



----------------------------------------------------------------------
T h e Q B N e w s P r o f e s s i o n a l L i b r a r y
----------------------------------------------------------------------

Popup Windows by Christy Gemmel

By now, you should have a good idea of how assembly-language can be
used with your QuickBASIC programs. The routines we've developed for
our library, so far, are short and simple, but they've already given
you a lot more power over your mouse and the video display. Hopefully
your appetite is whetted for more.

Well now we're starting on the heavy stuff. What I have for you next
is a complete Window Management System. Windows can be popped up at
any location on the screen, overlayering each other if you require.
They can be displayed in any combination of colours or attributes and
come in a variety of border styles. They can have shadow, to give a
three-dimensional effect, and you can zoom them onto the screen for a
really slick effect...

What are Windows? If you've used the QuickBASIC environment at all,
then you've used Windows. When you press <ALT><F> to bring down the
File Menu, the list of options presented there is in a window. Notice
how any characters which were hidden when the menu appeared, are
restored, intact, after you've made your choice and the menu window
vanishes. Windows are areas of the screen which are used to hold
transient data and messages to the user. They make the most of the
limited display space available and remove the need to be constantly
redrawing the screen each time your program communicates with the
outside world. Properly presented, windows can give the illusion of
multi-tasking, even on a single-processor machine like your PC.

Nowadays, no program worth its' salt can be without a window of some
kind. If YOUR program is going to stand out amongst all the others,
however, they've got to be done professionally. Your windows must
appear instantly and vanish, just as quickly, when no longer needed.
They must be as large or as small as is necessary, for the data which
you need to display, and you should have a plentiful supply, enough
for all the possible circumstances that your program might encounter.
High-level languages, unfortunately, are just not fast enough to meet
all these requirements.

Looks like it'll have to be assembler again ...

I'll start by explaining how windows work. You've already seen with
FASTPRINT, how large amounts of text can be output directly to the
video display in a flash. That bit shouldn't be any bother to us now.
For the rest, the main problem seems to be how to restore the
original screen contents when we take the window away, especially if
we have windows overlapping on the same portion of the screen.

The way to do it is this. First we establish a buffer in memory,
large enough to hold the total contents of all the windows we plan to
have on the screen at any one time. Then, just before we pop up a

The QBNews Page 6
Volume 2, Number 4 December 22, 1991

window, we must copy the contents of the screen rectangle which will
be covered by that window, into the buffer we have reserved. This
way, when we have have finished with the window, all we have to do is
to copy the original data from the buffer, back into the original
rectangle it came from.

Sounds easy? Well it is. The trick is in keeping track of which lot
of buffer data corresponds to which window on the screen and where to
put it all when we put it back. The rest is just byte shifting.

How big does the buffer need to be? It depends on the both the
number and the size of the windows which we plan to use. Remember
that each character displayed on the screen takes up two bytes of
information, one for the ASCII character code and the other for its'
associated attribute. So a window 20 columns wide by 12 rows high
would need (20 x 12) * 2 = 480 bytes of storage to hold the
information under it. Our window driver will contain 16K of internal
storage, the equivalent of 4 full screens, less a small amount of
overhead, enough for most reasonable applications.

To make things really easy, we'll operate the buffer on the stack
principle, so that the most recently displayed window is always the
first one to be removed. This is known as the LIFO method (Last In,
First Out), and it prevents the possibility of gaps appearing in our
buffer as windows are removed in different sequence from the one in
which they were created. You wouldn't believe the memory management
problems THAT can cause!

What calling conventions should we use? We'll need to define the
screen rectangle which the window will occupy, also the display
attribute, since we want to pop up different coloured windows for
different types of messages. How about border style? In the PANEL
subprogram we used two types of border, single and double lines,
perhaps we should offer a wider choice this time, here are some
possible permutations.

ÚÄÄÄÄ¿
³ 1. ³ Single-lined box all round the window
ÀÄÄÄÄÙ
ÉÍÍÍÍ»
º 2. º Double-lined box all round the window
ÈÍÍÍͼ
ÕÍÍÍ͸
³ 3. ³ Single vertical, double horizontal
ÔÍÍÍ;
ÖÄÄÄÄ·
º 4. º Single horizontal, double vertical
ÓÄÄÄĽ

An argument of zero can be used for plain windows, without borders.
They might be useful sometimes.

Is that the lot? Wait a minute though, a common application of
windows is for Pull Down Menus, like the QuickBASIC File Menu which

The QBNews Page 7
Volume 2, Number 4 December 22, 1991

you get by pressing <ALT><F>. If we're going to cater for this sort
of thing we need to include border styles which merge into a menu bar
at the top, these for instance;

ÍÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÍÍ ÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍ
³ ³ º º
³ ³ º º
³ 5. ³ º 6. º
³ ³ º º
³ ³ º º
ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍ; ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ
ÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄ ÄÄÒÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÒÄÄ
³ ³ º º
³ ³ º º
³ 7. ³ º 8. º
³ ³ º º
³ ³ º º
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽ

The SHADOW switch (Parameter 7), will be used to add a black shadow
underneath the window, Giving it a three dimensional effect. Setting
P7 to 1, puts the shadow on the left-hand side. Setting P7 to 2 puts
it on the right. Any other value prevents shadow.

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÖÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ·
Û³ ³ º ºÛ
Û³ ³ º ºÛ
Û³ Left Shadow ³ º Right Shadow ºÛ
Û³ ³ º ºÛ
Û³ ³ º ºÛ
ÛÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÓÄÄÄÄÄÄÄÄÄÄÄÄÄÄĽÛ
ßßßßßßßßßßßßßßßßß ßßßßßßßßßßßßßßßßß

Setting Parameter 8 to a non-zero value will cause the window to ZOOM
onto the screen. What this means is that, starting at a point source,
successively larger versions of the window will be drawn until it is
the size required. The process is extremely fast and impressive, and
will add a very professional touch to your programs.

Like all assembly-language routines linked to QuickBASIC programs,
our window code must use the Medium memory model. We will also be
calling a number of external routines which are listed here. Some of
these you will recognise from the FASTPRINT article. Source code for
the others is provided with this issue.

.model medium

extrn Delay:proc
extrn Explode:proc
extrn ScreenAddress:proc
extrn ScreenCopy:proc
extrn ScreenWrite:proc
extrn VideoType:proc
extrn WriteByte:proc

The QBNews Page 8
Volume 2, Number 4 December 22, 1991


The program will consist of two seperate modules, one is to draw the
window on the screen and the other to remove it when it is no longer
required. Both routines will be called from QuickBASIC, so they must
be declared PUBLIC.

public PopUp, ShutUp

.code

This program uses quite a lot of internal data. Let's declare it now
and get it over with. For now, notice the buffer used for holding
screen data, I have set this to 16KB (4000 Hex bytes) to give us room
for lots of windows. If you find that this is too big (or too small),
set it to your own value. No other changes are necessary.

Ulc label word ; Upper left co-ordinate
TlRow db ? ; Top left screen row
TlCol db ? ; Top left screen column
Lrc label word ; Lower right co-ordinate
BrCol db ? ; Right column of window
BrRow db ? ; Bottom row of window
Area label word
Breadth db ? ; Window width (inc shadow)
Height db ? ; Window Height (inc shadow)
ToDo label word
Cols2do db ? ; Columns to restore
Rows2do db ? ; Rows to restore
Rows db ? ; Screen length in rows
Columns db ? ; Screen width in columns
Increment dw ? ; Interval between rows
BuffPtr dw ? ; Pointer to current buffer
BuffTop dw ? ; Offset of first buffer row
BuffEnd dw ? ; Offset of last buffer row
WinTop dw ? ; Offset of first screen row
WinEnd dw ? ; Offset of last screen row

TopLeft label byte
db ' ÚÉÕÖÑËÂÒ' ; TL Corner characters
TopRight label byte
db ' ¿»¸·ÑËÂÒ' ; TR Corner characters
BotLeft label byte
db ' ÀÈÔÓÔÈÀÓ' ; BL Corner characters
BotRight label byte
db ' Ù¼¾½¾¼Ù½' ; BR Corner characters
Vertical label byte
db ' ³º³º³º³º' ; Vertical characters
Horizontal label byte
db ' ÄÍÍÄÍÍÄÄ' ; Horizontal characters

Buffer label byte ; Start of screen buffer
db 4000h dup(0)
BufferTop dw 0 ; End of screen Buffer


The QBNews Page 9
Volume 2, Number 4 December 22, 1991

Here's our introduction with BP being set, as usual, to point to the
stack. Once we've used it to obtain all the parameters, we are going
to point DS to our own local data. We need to save its' original
contents, therefore, so that they can be reset on return. ES will be
used to point to video memory so we'd better save that as well,
likewise the two Index registers.

PopUp proc far
push bp ; Save Base pointer
mov bp,sp ; Establish stack frame
push ds ; Preserve segment
push es ; registers and
push di ; index
push si ; pointers

You will remember this call from the FASTPRINT article. VIDEOTYPE is
a routine which we wrote to collect important information about the
kind of video display the host computer has. In this case we used it
to determine the number of rows and columns the screen is set to. The
routine also found the address of the video display buffer and number
of the video status port and has stored this information, internally,
in its' own module. We will need it later.

call VideoType ; Get video parameters

POPUP and SHUTUP, The two routines in this module, do not use string
data as arguments so we do not need to write a seperate version for
BASIC 7 using far strings. All the parameters that we will be using
are passed BY VALUE on the stack, so we do not need to keep our DS
register pointing to QuickBASIC's Data Segment (DGROUP). Let's point
it to our own local data so that we can access it more easily.

push cs ; Align Code and
pop ds ; Data segments

Many QuickBASIC programmers who are new to Assembly-language assume
that the DS register must be kept pointing to DGROUP while arguments
are read off the stack. This is only true when it is the address of
arguments which are passed, by reference, (QuickBASIC's default) and
the routine must go looking in DGROUP for the variables themselves.
Values read directly from stock, using the BP register as a pointer,
are indexed through the SS (Stack Segment) register, so DS can be
pointing elsewhere.

We will store the data that VIDOTYPE collected, locally, in our own
code segment. This saves using any of QuickBASIC's own data space
which, even if you use BASIC 7 is always at a premium. Aligning the
Code and Data registers allows us to store variables here without
having to use nasty segment override instructions and reduces
overhead considerably.

mov Rows,bl ; Store screen height
mov Columns,ah ; Store screen width
mov al,ah ; Transfer number of

The QBNews Page 10
Volume 2, Number 4 December 22, 1991

xor ah,ah ; columns to AX
shl ax,1 ; and convert
mov Increment,ax ; to bytes

While we have the screen width handy in AH, we may as well take the
opportunity to calculate the increment between rows for when we start
drawing vertical lines. Since there are two bytes per column, one
each for the character and it's display attribute, we must multiply
the number of columns by two. Shifting each bit of the number to the
left with SHL does this just as effectively and much faster than the
MUL instruction and saves having to use another register.

We've some chores to do before we can start on the interesting bits,
Just as we did in FASTPRINT, we must first examine the four arguments
which define the rectangle over which the window will appear, testing
them for legal values;

mov al,[bp+20] ; Get top-left row
dec al ; Make it base zero
cmp al,0 ; Check for
jge Pop_01 ; legal
xor al,al ; values
Pop_01:
mov TlRow,al ; Save top-left row
mov al,[bp+18] ; Get top-left column
dec al ; Make it base zero
cmp al,0 ; Check for
ja Pop_02 ; legal
mov al,1 ; values
Pop_02:
mov TlCol,al ; Store top-left column
mov al,[bp+16] ; Get window height
cmp al,2 ; Check for
ja Pop_03 ; legal
mov al,3 ; values
Pop_03:
mov [bp+16],al ; Store window height
mov al,[bp+14] ; Get window width
cmp al,2 ; Check for
ja Pop_04 ; legal
mov al,3 ; values
Pop_04:
mov [bp+14],al ; Store window width
mov al,TlRow ; Get start row
mov ah,[bp+16] ; Get number of rows
add al,ah ; Add 'em together
cmp al,Rows ; Out of bounds?
jb Pop_05 ; No, carry on
jmp Pop_38 ; Else abort
Pop_05:
dec al ; Store bottom
mov BrRow,al ; row number
mov al,TlCol ; Get start column
mov ah,[bp+14] ; Get number of columns

The QBNews Page 11
Volume 2, Number 4 December 22, 1991

add al,ah ; Add 'em together
cmp al,Columns ; Out of bounds?
jb Pop_06 ; No, carry on
jmp Pop_38 ; Else abort
Pop_06:
dec al ; Store rightmost
mov BrCol,al ; column number

Unlike the first two, the second pair of arguments do not give us the
coordinates of the lower-right corner directly. To make it easier for
our users, we'll accept the HEIGHT of the window (in rows) and the
WIDTH of the window (in columns), instead. We must make a couple of
small calculations to ensure that the window fits within the borders
of the screen, not forgetting to allow for shadow.

There are more parameters to come. The attribute value doesn't need
to be checked, since we will only be using the least-significant byte
of the integer value passed and all possible values (0-255) that this
can contain are legal. From here on, we won't abort if an illegal
argument is passed to us, instead we'll use a default. The default
for BORDER type is 1, a single-lined border.

mov al,[bp+10] ; Get required border type
cmp al,0 ; Check
jb Pop_07 ; for
cmp al,8 ; legal
ja Pop_07 ; values
jmp short Pop_08
Pop_07:
mov byte ptr [bp+10],1 ; Set default (single line)
Pop_08:
mov al,[bp+8] ; See if shadow is required
cmp al,0 ; Check
jl Pop_09 ; for
cmp al,4 ; legal
ja Pop_09 ; values
jmp short Pop_10
Pop_09:
mov byte ptr [bp+8],0 ; Set default (no shadow)
Pop_10:
cmp word ptr [bp+6],0 ; Check for
jge Pop_11 ; legal
mov word ptr [bp+6],20 ; values

The default for SHADOW is zero - no shadow. With ZOOM, the argument
is now used as a delay counter in milliseconds. Previous versions of
this routine treated any non-zero value as logical TRUE and generated
a fixed delay count. To cater for programs which still expect this, I
have translated any negative ZOOM values (eg -1) into a delay of 20,
which approximates to the previous ZOOM speed.

This, incidently, is an important point to bear in mind when you are
writing libraries for commercial (or Shareware) release. Whenever you
update an existing routine, remember that your current users may have

The QBNews Page 12
Volume 2, Number 4 December 22, 1991

to convert their old programs before they can use them with your new
version. If you can make this job as painless as possible they will
bless you for it.

Pop_11:
mov ax,1 ; Initialise
push ax ; millisecond
call Delay ; delay routine

ZOOM, the speed at which the window will explode onto the screen, is
controlled by the millisecond delay value we are passed. The smaller
this number, the faster the explosion. We need, however to initialise
the actual delay routine which is in our other module, DISPLAY.ASM.
Since initialisation, itself, takes a little time it is best to do it
here, where it won't be noticed, rather than add its' overhead to the
first appearance of our window.

Some more calculation is necessary, to obtain the co-ordinates of the
window. If it is going to have shadow then an extra row and column of
screen data will have to be saved in the buffer. Better work out the
area of the window, so we can check if there is enough space left in
the buffer. Multiply the number of rows in AX by the number of
columns in BX, this leaves the product in AX. Double AX again to
account for attribute bytes and transfer the result to CX.

xor ax,ax ; Get number of rows
mov al,[bp+16] ; into AX
xor bx,bx ; Get number of columns
mov bl,[bp+14] ; into BX
cmp byte ptr [bp+8],0 ; Shadow required?
jz Pop_12 ; No, skip next bit
inc al ; Add a row
inc bl ; and a column
Pop_12:
mov Height,al ; Store adjusted height
mov Breadth,bl ; Store adjusted width
mul bl ; Find area
shl ax,1 ; in bytes
mov cx,ax ; Transfer to CX
mov si,offset Buffer ; Start of screen stack

That big, huh? Let's see if it will fit.

Perhaps I should explain how the buffer is organised. Since each
block of screen data stored in it is likely to be of a different
size, it is necessary to use the concept of a LINKED LIST. By this
method, every block in the buffer begins with a pointer to the next
block. In searching for a free block, we follow the chain along the
list until we find an empty pointer. Any space following this should
be available for use. Note that, initially, the buffer area was set
to zeroes.

In addition to the next-block pointer, we will store the offset
address of the top left corner of each window in the buffer. The

The QBNews Page 13
Volume 2, Number 4 December 22, 1991

height of the window, in rows, and it's width, in columns (including
any extra allowed for shadow), will also be stored. This should be
enough to specify the screen rectangles we need to save. Every block
of buffer storage, therefore, will contain the following data ...

width height
³ ³
ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÄ ÄÄÄÄÄÄ¿
³ 2 bytes ³ 2 bytes ³ 2 bytes ³ Variable length ³
ÀÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄ ÄÄÄÄÄÄÙ
³ ³ ³ ³
Pointer to Offset of Dimensions Storage for screen data
next block UL Corner of window
of window (including
any shadow)

The SI register is now pointing to the start of the buffer, so we can
examine the first two bytes right away. If they are empty (set to
zero), then the whole buffer is empty and we can begin storing our
block of screen data at once. If however, the first two bytes contain
data, then we can assume that it is a pointer to the beginning of the
NEXT block, so we load SI with this address and repeat the process.
Only when SI points to a word of zeroes, do we we have free space to
store our screen.

Pop_13:
cmp word ptr [si],0 ; Is anything there?
jz Pop_14 ; No, must be free space
mov si,[si] ; Point to next block
jmp Pop_13 ; and try again
Pop_14:
mov ax,si ; Point AX to entry
add ax,6 ; Allow for pointers
add ax,cx ; and area to be saved
mov dx,offset BufferTop ; Point DX to end of stack
cmp ax,dx ; Enough space left?
jb Pop_15 ; Yes, Carry on
jmp Pop_38 ; Otherwise abort

Before we begin copying data to the buffer, we must make sure that
there is enough space left to hold it, for all we know there may be
other windows up on the screen. We have already calculated the size
of the block we need to save. Add another six bytes for our own
pointers, then add the result to the offset address of the current
block. If the resulting address is past the end of the buffer, then
there is not enough room for the new window and there is no point in
proceeding any further.

If there is room for another window, then we can go ahead. Since AX,
as a result of the last calculation, is already holding the address
of the first byte past our save area, we can store this immediately,
as the next-block-pointer.

Pop_15:

The QBNews Page 14
Volume 2, Number 4 December 22, 1991

mov [si],ax ; Set pointer to next block

SCREENADDRESS is one of the support routines we developed for our
FASTPRINT utility two issues back. We used it to calculate the screen
address of the position where printing was to start. This time we are
looking for the address of the top-left corner of the rectangle which
will be covered by our window. If the window is going to have shadow
on the left, we must also save an extra column on the left-hand side.

mov ax,Ulc ; Get row-column co-ordinates
call ScreenAddress ; Convert to memory address
mov WinTop,di ; Save it for later
test byte ptr [bp+8],1 ; Left shadow?
jz Pop_16 ; No, skip next bit
dec di ; One column
dec di ; to the left
Pop_16:
inc si ; Bump buffer
inc si ; pointer
mov [si],di ; Store it in buffer
inc si ; Bump buffer
inc si ; pointer
mov ax,Area ; Get panel Area
mov [si],ax ; Store them in the buffer
inc si ; Bump pointer to
inc si ; screen storage block

Once we have the address, it is stored, in the save buffer, at the
word following the next-block-pointer. Finally, the third word in the
save buffer is loaded with the height and width of the rectangle
we are going to save.

It is worth drawing your attention to the method we just used to
access the dimensions of the rectangle. When we originally stored the
height and width after they were calculated, we saved them as BYTE
values. Now we come to use them we are able to copy them both, as a
single WORD, into AX with one instruction. One of the nice features
of MASM is that it allows us to define the same block of memory in
several different ways:

Area label word
Breadth db ? ; Window width (inc shadow)
Height db ? ; Window Height (inc shadow)

In this example, BREADTH and HEIGHT are labels which define single
bytes, but AREA, which also refers to the same location, is of size
WORD (2 bytes). By using this as a reference, we can load both values
directly into AX in one move, instead of the two which would be
necessary if we had to load AH and AL individually.

If you have stuck with me, through the examples in these articles, so
far, you will have realised that most of the donkey-work of assembly-
language programming, lies in setting up registers and data ready for
some operation which, in itself, only takes a few instructions. Such

The QBNews Page 15
Volume 2, Number 4 December 22, 1991

is the case here. We are going to use a nested loop to succesively
copy each column of each row of the screen rectangle which our window
will cover, into the memory buffer.

As well as being one of the four 8086 general-purpose registers, CX
can also be used as a loop counter. Our outer processing loop will
govern the number of rows copied. This value is already in AH. Let's
begin by copying it to CX.

xor cx,cx ; Get number of rows
mov cl,ah ; in CX

Before we start, we must swap over the pointers we have been using so
far. Currently the Data Segment register (DS) is pointing to the Code
Segment where our local data is. Now, however, we need to treat the
video display buffer as our Data segment because we are going to copy
that part of the screen which will lie underneath our window into the
buffer for safekeeping.

xchg di,si ; Swap pointers
push ds ; Point DS to
push es ; video segment
pop ds ; and ES to
pop es ; local data

The inner loop controls the number of columns to be copied. We'll use
CX as the counter for this as well. Save the row count on the stack,
temporarily, while we load the column count into CX. Remember this
number includes an extra column for shadow, if specified. We'll also
push SI, which contains the screen starting address, for reasons
which will become clear.

Pop_17:
push cx ; Save row count
push si ; Save screen pointer
mov cl,cs:Breadth ; Set column count

Now we can start moving data. SI is currently pointing to the address
in video memory, where the first character to be moved is located. DS
has the segment address of video memory. DI is pointing to the start
of the first free block in our storage buffer, relative to the ES
register. Since the actual shifting of bytes is a task that is likely
to need doing on other occasions, we have dedicated a special routine
to it, SCREENCOPY. This is listed in the new version of DISPLAY.ASM
which is provided with issue.

Pop_18:
call ScreenCopy ; Copy word from screen
loop Pop_18 ; For length of row

The LOOP instruction decrements the value in CX and then returns
control to the statement following the indicated label. It does this
until the value of CX is reduced to zero, after which the program
falls through to the next line. Since CX was originally loaded with

The QBNews Page 16
Volume 2, Number 4 December 22, 1991

the number of columns in the row, this only happens when a full row
has been copied.

Back in the outer loop, we retrieve the original screen pointer from
the stack and add the row increment value which we calculated earlier
to it. This value may be 80, 160 or 264 bytes, depending upon whether
our screen is set to 40, 80 or 132 columns, and will make SI point to
the start of the next row to be moved. We also retrieve the row count
into CX, so that the second LOOP instruction can be used to repeat
the whole procedure until every row has been done.

pop si ; Bump pointer
add si,cs:Increment ; to next row
pop cx ; Recover row count
loop Pop_17 ; For each row

That's the first stage complete. We have copied the whole of the
rectangle which our window will cover, into the storage buffer
reserved for it. It's time to start drawing the window, but before
we start we must put our segment registers back where they belong.

push es ; Realign Code and
pop ds ; Data segments

If our caller has requested it, we must ZOOM the window onto the
screen. This means drawing not just one window, but a whole raft of
them, each a little larger than the last, until we have one of the
size required. In assembly-language, the whole process is so fast
that it seems to occur in one continuous movement. It is so fast, in
fact, that we will have to deliberately slow it down, so that our
audience can appreciate it.

The routine which does all this deserves more than just a bit-part in
this article so I have given it a wider audience by making it a self-
contained procedure in its' own right, one which can be called direct
from QuickBASIC. You can find the source code for EXPLODE in the new
version of DISPLAY.ASM which is provided with this issue. For now we
just need to be aware that it is designed to use QuickBASIC's calling
conventions, so we had better emulate them and pass our arguments on
the stack.

xor ah,ah ; Pass all parameters in AX
mov al,TlRow ; Get upper-left row
inc al ; Must use BASIC numbering
push ax ; Pass the argument
mov al,TlCol ; Get upper-left column
inc al ; Must use BASIC numbering
push ax ; Pass the argument
mov al,BrRow ; Get lower-right row
inc al ; Must use BASIC numbering
push ax ; Pass the argument
mov al,BrCol ; Get lower-right column
inc al ; Must use BASIC numbering
push ax ; Pass the argument

The QBNews Page 17
Volume 2, Number 4 December 22, 1991

push [bp+12] ; Pass display attribute
push [bp+6] ; Pass speed value
call Explode ; Zoom the window

Notice that we are calling EXPLODE, even if our caller specified no
ZOOM for this window. This is not a mistake since no ZOOM is the same
as a delay count of zero and just means that the window panel will
appear instantaneously.

We're not finished yet, though. We've still got to draw the border.
That is, if one is required.

mov ax,Ulc ; Get row/column co-ordinate
call ScreenAddress ; Convert to memory address
cmp byte ptr [bp+10],0 ; Border required?
ja Pop_19 ; Yes, draw it
jmp Pop_23 ; Else check for shadow

The border type was the sixth parameter supplied by the calling
program. We'll load it into BX so that it can be used as an index to
the list of graphics characters stored in our local data. While we're
at it, we'll load the display attribute required into AH

Pop_19:
xor bx,bx ; Border type
mov bl,[bp+10] ; to BX
mov ah,[bp+12] ; Attribute to AH
push di ; Save screen offset

We've already worked out the address of the top-left corner of the
window, no need to calculate it again. Now to get the top-left corner
character.

TopLeft label byte
db ' ÚÉÕÖÑËÂÒ' ; TL Corner characters

Border types --> 012345678 <-- Argument in BX

TOPLEFT is a label which refers to the string of extended ASCII top-
left characters in our program data. Since BX has been set to the
number of the border type required, we can use it as an index into
the character string. The following instruction, therefore, loads AL
with the byte stored at the address pointed to by TOPLEFT + BX.

mov al,TopLeft[bx] ; Border character to AL
call ScreenWrite ; Send it to the screen

Once the character is in AL we can send it, and the attribute byte in
AH, to the screen address pointed to by ES:DI. Our old friend, the
SCREENWRITE procedure performs this for us.

We now have to put the horizontal border characters along the top row
of the window. Since SCREENWRITE updates the screen pointer after it
writes a character, DI is already pointing to the correct address for

The QBNews Page 18
Volume 2, Number 4 December 22, 1991

us to continue.

mov al,Horizontal[bx] ; Border character to AL
xor cx,cx ; Window width
mov cl,[bp+14] ; to CX
dec cl ; Subtract
dec cl ; corners

Load the appropriate ASCII value into AL, and the number of
characters to write into CX, we can do this with a loop.

Pop_20:
call ScreenWrite ; Send it to the screen
loop Pop_20

Finally, load the Right-hand corner character and send that out as
well. That's the top row done.

mov al,TopRight[bx] ; Border character to AL
call ScreenWrite ; Send it to the screen

Can we do the horizontal borders as a loop? 'course we can!

pop di ; Recover offset
add di,Increment ; Bump to next row
mov al,Vertical[bx] ; Border character to AL
mov cl,[bp+16] ; Window height to CX
dec cl ; Subtract top and
dec cl ; bottom rows

Fortunately, we had the foresight to store our original starting
position on the screen before SCREENWRITE incremented it. All we need
do is retrieve it and add our increment value to point DI to the
start of the next row. Let's get the correct vertical bar character
into AL and then set CX to control the number of rows we are going to
loop through. We've already done the top row and the bottom will
be different again, so we can subtract two from the total number
of rows.

Pop_21:
push cx ; Save counter
push di ; Save screen pointer
call ScreenWrite ; Left border
mov cl,[bp+14] ; Get window width
dec cl ; Point
dec cl ; to
shl cx,1 ; rightmost
add di,cx ; column
call ScreenWrite ; Right border
pop di ; Recover screen pointer
add di,Increment ; Bump to next row
pop cx ; Recover row count
loop Pop_21 ; For each row


The QBNews Page 19
Volume 2, Number 4 December 22, 1991

There, that's done. Notice how we used CX again, in the middle of the
loop, to calculate the offset of the character at the right border.
Finally, apart from the corner characters, the bottom row is a repeat
of the top ....

mov al,BotLeft[bx] ; Border character to AL
call ScreenWrite ; Send it to the screen
mov al,Horizontal[bx] ; Border character to AL
mov cl,[bp+14] ; Get window width
dec cl ; Subtract
dec cl ; corners
Pop_22:
call ScreenWrite ; Send it to the screen
loop Pop_22
mov al,BotRight[bx] ; Border character to AL
call ScreenWrite ; Send it to the screen

... and that's the window AND border done. Is that the lot?

Pop_23:
cmp byte ptr [bp+8],0 ; Shadow required?
ja Pop_24 ; Yes, handle it
jmp Pop_37 ; Else wrap everything up

Not quite, we've still got to deal with SHADOW.

Pop_24:
mov di,WinTop ; Back to top-left corner
add di,Increment ; start at next row down
xor cx,cx ; Get window width
mov cl,[bp+14] ; into CX
shl cx,1 ; Include attribute bytes
cmp byte ptr [bp+8],2 ; Solid shadow?
ja Pop_30 ; No, make it transparant
cmp byte ptr [bp+8],1 ; Left shadow?
jne Pop_25 ; No, must be right
dec di ; Left one
dec di ; column
jmp short Pop_26 ; Get to work

To give the right three-dimensional effect, the shadow will have to
begin one row down from the top of the window. Is it going to be a
LEFT or a RIGHT shadow? If LEFT, we point DI one column to the Left
of the window, if RIGHT, then DI is set to point one column past the
Right-hand border. We must also distinguish between transparent and
solid shadow. The first changes the display attribute of the shadowed
text so that it is still visible, albeit dimly, while the second type
blanks out underlying characters completely.

Pop_25:
add di,cx ; Offset past right-hand edge
Pop_26:
mov ax,720h ; Space on black background
push cx ; Save width for now

The QBNews Page 20
Volume 2, Number 4 December 22, 1991

mov cl,[bp+16] ; Rows to shadow

The display attribute we will use for solid shadow is 7. Normal white
text on a black

background. The actual character is ASCII 32(20 Hex), 
a blank space. The shadow will be as tall as the window itself, but,
since we start one row down, it will extend past the bottom. Here's
the loop that does it.

Pop_27:
call ScreenWrite ; Send it to the screen
add di,Increment ; Bump to
dec di ; next
dec di ; row
loop Pop_27 ; For height of window
pop cx ; Recover window width
sub di,Increment ; Back up one row
cmp byte ptr [bp+8],1 ; Left shadow?
je Pop_28 ; No, must be right
sub di,cx ; Jump back to start of row
inc di ; Begin one column
inc di ; in from left
Pop_28:
shr cx,1 ; Convert width to columns
Pop_29:
call ScreenWrite ; Put black shadow
loop Pop_29 ; under the bottom row
jmp short Pop_37 ; Branch to the exit

If we're doing TRANSPARENT shadow, then we don't change the character
byte at all. Instead we skip to the attribute byte and change that to
a value of eight which produces dark grey text on a black background.
WRITEBYTE is a variant of SCREENWRITE which outputs a single byte to
the display instead of a character and attribute (2 bytes). Once more
it is listed for you in the new version of DISPLAY.ASM.

Pop_30:
cmp byte ptr [bp+8],3 ; Left shadow?
jne Pop_31 ; No, must be right
dec di ; Left one
dec di ; column
jmp short Pop_32 ; Get to work
Pop_31:
add di,cx ; Offset past right-hand edge
Pop_32:
inc di ; Bump to attribute byte
mov al,8 ; Dark grey foreground
push cx ; Save width for now
mov cl,[bp+16] ; Rows to shadow
Pop_33:
call WriteByte ; Set display attribute
add di,Increment ; Bump to
dec di ; next row
loop Pop_33 ; For height of window
pop cx ; Recover window width

The QBNews Page 21
Volume 2, Number 4 December 22, 1991

sub di,Increment ; Back up
inc di ; one row
cmp byte ptr [bp+8],3 ; Left shadow?
jne Pop_34 ; No, must be right
dec di ; Back up one column
jmp short Pop_35 ; Start on bottom row
Pop_34:
sub di,cx ; Jump back to the
inc di ; beginning of the row
Pop_35:
shr cx,1 ; Convert width to columns
Pop_36:
call WriteByte ; Set display attribute
inc di ; Bump past character byte
loop Pop_36 ; For width of window

And there it is. One window up on the screen, just as our calling
program requested. All that remains is to tidy up and go home.

Pop_37:
xor ax,ax ; Report no error
Pop_38:
pop si ; Clean up the stack
pop di
pop es
pop ds
pop bp
ret 16 ; Return to caller
PopUp endp

There you are. A superfast, full feature window generator, worthy to
stand alongside all those professional programs on your shelf. Bill
Gates, move over!

Wait a minute though, it's all very well being able to pop up windows
of all shapes and sizes. We also need to take them down again, when
they're finished with. And what about all that screen data stored in
the buffer? We've still got to put it back again.

That's the business of the second routine in this module. Luckily,
SHUTUP is not nearly as big as POPUP. All it has to do, in fact, is
find the last block of screen data that was stored in the buffer, and
restore it to it's original location on the screen, wiping out the
window which covers it in the process. To make it a little fancier,
however, I have added a reverse ZOOM option. This restores the screen
data stored in the buffer selectively, working from the outside edge
of the window inwards to the centre. This makes the window appear to
implode and, depending on the speed parameter supplied, gives a very
slick effect.

ShutUp proc far
push bp ; Save base pointer
mov bp,sp ; Establish stack frame
push ds ; Save segment

The QBNews Page 22
Volume 2, Number 4 December 22, 1991

push es ; registers and
push di ; index
push si ; pointers
push cs ; Align code and
pop ds ; data segments
cld ; Clear direction forward
call VideoType ; Get video parameters
mov al,ah ; Transfer number of
xor ah,ah ; columns to AX
shl ax,1 ; and convert
mov Increment,ax ; to bytes

SHUTUP is going to access the same data that was used by POPUP and,
since this is at the top of our local code, we must begin by pointing
the DS:SI registers to it.

mov si,offset Buffer ; DS:SI==> screen buffer
xor ax,ax ; Initialise
push ax ; back pointer

By default, all offset addresses are relative to the DS register. Now
we have it positioned correctly, we can set SI to point to the start
of the save buffer. What follows is very similar to the routine, in
POPUP, which searched for the next free block. Remember that the
first two bytes of each block is a pointer to the next one in the
chain. Just as before, we must follow the pointers along until we
come to an empty block.

Shut_01:
cmp word ptr [si],0 ; Is anything there?
jz Shut_02 ; No, we're at the end
pop ax ; Retrieve pointer
push si ; Save present pointer
mov si,[si] ; Point to next block
jmp Shut_01 ; Keep searching

If the first word in the buffer is zero, of course, the buffer is
empty and there are no windows to restore, (it was a waste of time
calling us!). Otherwise we end up pointing to the the first free
block.

Hang on a minute, though, it's the PREVIOUS block we want. The one
which contains the last window stored ...

Oh, I get it! That's why we kept saving the next-block-pointer. All
we have to do is pop the last one back into SI, and we're pointing to
the previous block again.

Shut_02:
pop si ; Retrieve last pointer
cmp si,0 ; Was there anything?
jnz Shut_03 ; Yes, proceed
mov ax,1 ; Else set Errorlevel
jmp Shut_14 ; and abort

The QBNews Page 23
Volume 2, Number 4 December 22, 1991


Right, we've found the block to be restored. Now we need to extract
the screen location where it's going to be restored to and the height
and width of the original rectangle. If you remember they were stored
along with the original data. Notice we store the window dimensions
twice, once for reference and once for use as a running total as we
gradually restore larger portions of the screen below the window.

Shut_03:
mov BuffPtr,si ; Save buffer pointer
inc si ; Bump to
inc si ; next entry
mov di,[si] ; ES:DI==> screen location
mov WinTop,di ; Save screen offset
inc si ; Bump to
inc si ; next entry
mov ax,[si] ; Get panel dimensions
mov Area,ax ; Store them
mov ToDo,ax ; for later
inc si ; Bump to screen
inc si ; storage block
mov BuffTop,si ; Save buffer pointer

The next step is to calculate the addresses in the save buffer and on
the screen, where the LAST row of the window begins. Using this and
the address of the first row, which SI is now pointing to, as our
starting points, we can work inwards on successive passes through the
restore loop.

xor bx,bx ; AX has window width
xchg bl,ah ; BX has window height
dec bl ; less one row
shl bx,1 ; Convert to bytes
mul bx ; Calculate offset
add ax,si ; of the last row
mov BuffEnd,ax ; Save this as well
mov ax,Increment ; Multiply screen width
mov bl,Height ; by window height
dec bl ; less one row
mul bx ; Result is relative offset
add ax,di ; Now convert it to the
mov WinEnd,ax ; absolute screen offset

Before beginning, we still need to point ES to the segment address of
video memory and, as ever, SCREENADDRESS proves its' worth. We had
better check that the delay count supplied is legal as well, just in
case the user has passed a negative argument or something silly like
that. Since we are treating the argument as an unsigned value a delay
of -1 would be interpreted as 65535 which, even at assembler speeds,
would take a L-O-N-G time.

xor ax,ax ; Get video segment
call ScreenAddress ; and CRT status port
cmp word ptr [bp+6],0 ; Check for

The QBNews Page 24
Volume 2, Number 4 December 22, 1991

jge Shut_04 ; legal delay
mov word ptr [bp+6],20 ; values

Here is the start of the outer loop. We begin by loading our source
and destination index registers, SI and DI, with the addresses of the
top row of the window, SI being its' location in the save buffer and
DI it's location on the screen. CX, which, as always, is the loop
counter, gets the number of columns in this row.

Shut_04:
xor cx,cx ; Clear counter
mov si,BuffTop ; DS:SI==> first buffer row
mov di,WinTop ; ES:DI==> first screen row
mov cl,Cols2do ; Number of words to copy

We have already met SCREENCOPY above, when we used it to copy words
from the display into our save buffer. Now we are using it in the
opposite direction to move the data from the buffer back onto the
screen, repeating the process until each column of this row has been
restored.

Shut_05:
call ScreenCopy ; Send word to the screen
loop Shut_05 ; For length of row
dec Rows2do ; All rows done?
jnz Shut_06 ; No, carry on
jmp Shut_13 ; Otherwise depart

With this row restored we can decrement the count of rows to be done.
Eventually, when the count reduces to zero and sets the zero flag in
the flags register, we can take it as a sign that the window has gone
and branch out of the loop.

For now, however, we must carry on and restore the bottom row, using
very similar code to the top row routine, except that our index
registers are pointed to the bottom row of the window, both in the
buffer and on the screen.

Shut_06:
mov si,BuffEnd ; DS:SI==> last buffer row
mov di,WinEnd ; ES:DI==> last screen row
mov cl,Cols2do ; Number of words to copy
Shut_07:
call ScreenCopy ; Send word to the screen
loop Shut_07 ; For length of row
dec Rows2do ; All rows done?
jnz Shut_08 ; No, carry on
jmp Shut_13 ; Otherwise depart

A few sums to do now. We must take the our pointer to the start of
the save buffer, BUFFTOP, and add to it the number of bytes per row
of the window we are restoring. This will bump the buffer pointer to
the next row to be restored at the top of the window. Subtracting the
same number of bytes from BUFFEND, the last row pointer, will give us

The QBNews Page 25
Volume 2, Number 4 December 22, 1991

the buffer location of the next row to be restored at the bottom of
the window.

Shut_08:
xor ax,ax ; Clear AX
mov si,BuffTop ; Reset
mov al,Breadth ; pointer
shl al,1 ; to first
add si,ax ; buffer
mov BuffTop,si ; row
mov di,WinTop ; Do the same
add di,Increment ; for the first
mov WinTop,di ; screen row

A similar calculation must be made to obtain the screen addresses of
the new top and bottom rows. However this is a little easier, since
all we need do is add or subtract the row INCREMENT from the current
pointer values.

mov ax,BuffEnd ; Reset
mov cl,Breadth ; pointer
shl cl,1 ; to last
sub ax,cx ; buffer
mov BuffEnd,ax ; row
mov ax,WinEnd ; Do the same
sub ax,Increment ; for the last
mov WinEnd,ax ; screen row

We've removed the top and bottom rows, now it's time to do the left
and right columns. After our previous calculations, SI and DI are now
pointing to the top-left corner of what's left of the window, so we
can start by removing the column on the left-hand side.

Shut_09:
cmp si,BuffEnd ; End of buffer?
ja Shut_10 ; Yes, see if we've finished
call ScreenCopy ; Send word to the screen
xor ax,ax ; Clear AX again
mov al,Breadth ; Keep
dec al ; doing
shl al,1 ; it
add si,ax ; all
add di,Increment ; down
dec di ; the
dec di ; left
jmp short Shut_09 ; side
Shut_10:
dec Cols2do ; All columns done?
jz Shut_13 ; If so, depart
mov al,Cols2do ; How far to the
shl al,1 ; end of the row?
mov si,BuffTop ; Point SI to
add si,ax ; buffer data
mov di,WinTop ; Point DI to

The QBNews Page 26
Volume 2, Number 4 December 22, 1991

add di,ax ; screen offset

This done, we decrement the number of columns there are to do and, as
long as the remainder is not zero, add the result converted to bytes,
to our index registers. This points us to the top-right corner of the
window, which is now shrinking fast.

Away we go down the right-hand side......

Shut_11:
call ScreenCopy ; Send word to the screen
cmp si,BuffEnd ; End of buffer?
ja Shut_12 ; Yes, see if we're finished
xor ax,ax ; Clear AX again
mov al,Breadth ; Keep
dec al ; doing
shl al,1 ; it
add si,ax ; all
add di,Increment ; down
dec di ; the
dec di ; right
jmp short Shut_11 ; side
Shut_12:
dec Cols2do ; All columns done?
jz Shut_13 ; If so, depart

By the time we reach here we have done a complete circuit of our
window, lopping off a row or column on all four sides. If there is
still more to do, we must adjust the pointers to the screen and
buffer to take the reduced number of columns into account.

add BuffTop,2 ; Each subsequent
add BuffEnd,2 ; row starts
add WinTop,2 ; another word
add WinEnd,2 ; further in

Although it has taken a long time to describe, the actual processing
has been done in less than the twinkle of an eye. So if we want our
audience to actually see how clever we are, we must slow things down
to human speeds. It's a good job computers don't get bored....

push [bp+6] ; Pass speed value
call Delay ; Pause awhile
jmp Shut_04 ; Then do it all again

Well, to us it didn't take long. The window is gone and the screen is
back in its' original pristine state. Only one thing left to do. We
must also clear this block from our save buffer.

With our usual foresight, we saved the pointer to the start of the
block. Notice that we're retrieving it into DI, this time, not SI.
There's a good reason for this which I'll explain in a minute. In the
meantime, we need to calculate the length of the block, since we're
going to set every byte to zero.

The QBNews Page 27
Volume 2, Number 4 December 22, 1991


Shut_13:
mov di,BuffPtr ; Recover buffer pointer
mov cx,[di] ; Pointer to next block
sub cx,di ; Calculate length of block
inc cx

Amongst the many instructions built into the Intel 8086 series of
microprocessors, is a set of very powerful string manipulation
routines. The one we're going to use is STOSB (STOre Byte to String).
This stores the contents of the AL register into the address pointed
to by the ES:DI registers, and then increments DI. Not much in
itself, but if you prefix STOSB with the REP instruction, the process
is REPeated the number of times set by CX. This way we can fill a
large block of memory with a single line of code, instead of having
to set up an elaborate loop.

ES:DI is already pointing to the start of the block. CX contains the
length of the block, in bytes. All we have to do is load AL with the
value to fill, in this case zero.

push ds ; Point ES to Data Segment
pop es
xor ax,ax ; Clear AX
rep stosb ; Zero restored block
Shut_14:
pop si ; Clean up the stack
pop di
pop es
pop ds
pop bp
ret 2 ; Return to caller
ShutUp endp

end

That's it, we've done it. Our window manager is complete. Have you
still got the strength to add it to the library?

ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ GETTING IT ALL TOGETHER ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ

Two object files are provided. WINDOWER.OBJ contains the two window
procedures, POPUP and SHUTUP, described above. DISPLAY.OBJ contains
FASTPRINT and all the support routines used in my previous article,
along with EXPLODE and the other new routines added in this issue.

If you are using the BASIC 7 PDS with far strings, then, instead of
DISPLAY.OBJ you must use the object file DISPLAY7.OBJ which is also
provided. WINDOWER will work equally well with either version of the
compiler.

To produce a new version of your Assembly-Language Library, copy the

The QBNews Page 28
Volume 2, Number 4 December 22, 1991

object files along with your existing copy of ASSEMBLY.LIB into the
directory which contains the QuickBASIC Library Manager LIB.EXE. Then
issue the following command:

LIB ASSEMBLY +WINDOWER -+DISPLAY,ASSEMBLY.CAT;

This will add WINDOWER.OBJ to ASSEMBLY.LIB and replace the existing
version of DISPLAY.OBJ with the new one. The command also tells
LIB.EXE to generate an updated version of your library catalogue file
ASSEMBLY.CAT.

Producing the matching Quick Library, ASSEMBLY.QLB, is just as easy.
Using the new copy of ASSEMBLY.LIB you have just produced, type:

LINK /QU ASSEMBLY.LIB,,,BQLB45.LIB;

Notice that LINK.EXE can work just as easily with complete libraries
as it does with individual object files. The Quick Library support
file BQLB45.LIB, (QBXQLB.LIB if you use BASIC 7), must also be either
present, or on your environment search path.

To use the window routines in your programs you must include the
following declarations at the beginning of the source code:

DECLARE SUB PopUp(BYVAL Row%, BYVAL Col%, BYVAL Hght%, BYVAL Wdth%,_
BYVAL Attr%, BYVAL Brdr%, BYVAL Shdw%, BYVAL Zoom%)
DECLARE SUB ShutUp(BYVAL Speed%)

Then, whenever you want to pop up a window, issue a statement like
this ...

PopUp 4, 10, 8, 50, 48, 2, 1, -1

.. which produces a window with its top-left corner at row 4,
column 10. This window is eight rows high by fifty columns wide, it
has a black, double-lined border on a cyan background (if you have a
colour monitor), with shadow on the left-hand side and, when it
appears, it will Z-O-O-O-M onto the screen. You can pass variables or
expressions to POPUP as well as constants, but they must all evaluate
to integers, otherwise the routine may refuse to pop!

To get rid of the window, and restore the screen contents, use the
statement ...

ShutUp -1

Remember that SHUTUP always removes the most recent window.

EXPLODE, the external procedure which is called by POPUP to zoom the
window onto the screen, can also be called directly from QuickBASIC.
If you want to use it outside of the window routines, then you must
declare it seperately with the following statement:

DECLARE SUB Explode (BYVAL Y1%, BYVAL X1%, BYVAL Y2%, BYVAL X2%,_

The QBNews Page 29
Volume 2, Number 4 December 22, 1991

BYVAL Attr%, BYVAL Speed%)

Arguments: Y1% = Upper-left row of rectangle to be cleared
X1% = Upper-left column of rectangle
Y2% = Lower-right row of rectangle
X2% = Lower-right column of rectangle
Attr% = Display attribute or colour that rectangle
should be cleared to
Speed% = Speed (in milliseconds) of explosion.

The example program, WINDEM.BAS, uses EXPLODE whenever it needs to
clear text from a window on the screen, without having to remove the
window itself. It can also be used as a general-purpose routine for
whenever you need to selectively clear a part of the screen, without
effecting the rest of the display. You can disable the exploding part
by specifying a delay of zero.

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
If you have a problem getting these routines to work with your
system, or you have any comments or suggestions for future programs,
I would like to hear from you.

Christy Gemmell
22 Peake Road, Northfields
Leicester LE4 7DN
England
Tel. (044)-0533-767960

If transatlantic mail is too slow (or the telephone charge too high),
I can also be reached via:

Jim Kreyling (sysop)
Club-PC BBS
1217 Crescent Drive,
Smithfield, Va.23430
Tel. (804)-357-0357 BBS
Tel. (804)-357-9190 FAX or Voice

Club-PC are also US distributors of my Assembly-Language Toolbox for
QuickBASIC which contains a full set of window and display routines,
as well as lots of other useful features. Shareware versions are
available both for QuickBASIC 4.5 and BASIC 7 and can be downloaded
free from this board.

**********************************************************************
Christy Gemmell resides in England and was the major author of the
Waite Group book QuickBASIC Bible. His new book is The Waite Group's
QBASIC Bible. Christy also has a shareware called the Assembly
Language Toolbox for QuickBASIC. Christy can be reached in care of
this newsletter.
**********************************************************************



The QBNews Page 30
Volume 2, Number 4 December 22, 1991



----------------------------------------------------------------------
B e g i n n e r ' s C o r n e r
----------------------------------------------------------------------

Using COM 3 and COM 4 with QB by Dick Dennison

Quick Basic is limited in its handling of COM ports to COM1 and COM2.
This is becomming a recurring problem in these days of installed mouse
systems as a mouse installed on COM1 also eliminates COM3 (likewise
for COM2/COM4). So what do you do if you have to run your modem on COM4
and want to still use BASIC's OPEN COMx statements ? Simple. Stuff the
address for COM4 where COM2 used to be.

[Extracted from Pete Petrakis]

Published memory maps for PCs consistently claim that BIOS memory
addresses 0000:400-407 contain the addresses for serial port adapters 1
through 4 (COM1 to COM4). Thus, if you have four serial ports installed,
you should be able to see the following if you type D 0000:400 in
DEBUG:

0000:0400 F8 03 F8 02 E8 03 E8 02 . . .

You SHOULD be able to see that, but you probably won't!

The chances are that you will see no more than F8 03 F8 02, which
represent the port addresses for COM1 (3F8) and COM2 (2F8), and that
the positions for COM3 and COM4 will contain only zeroes. The problem
is that hardware that creates COM3 and COM4 is likely NOT to poke the
appropriate addresses for those ports into the BIOS communications data
area.

[End of Extract]

The addresses for the comports are:
COM Port Base Port Address

COM1 &H3F8
COM2 &H2F8
COM3 &H3E8
COM4 &H2E8


So (simply) what we want to do is:

Step 1. Save the current addresses in the BIOS communication
(&h400 - &h403) areas for later reset.

Step 2. Determine which port you need and stuff (poke) the
appropriate address into COM1 (&h400-&h401) or
COM2 (&h402-&h403).

Step 3. Use Basic's Open comx statements and do your thing.


The QBNews Page 31
Volume 2, Number 4 December 22, 1991

Step 4. Reinstall the old addresses.

The accompanying source code expects the user to supply the comport number
on the command line.

[EDITOR'S NOTE]
The source code for this article is in the file DIAL.ZIP.















































The QBNews Page 32
Volume 2, Number 4 December 22, 1991

The UEVENT Bug by Ray Crumrine

While experimenting with a third party library product that uses
SetUevent, I discovered the following bug in the QB (V4.5) UEVENT
routines. Microsoft assured me the bug has been fixed in BC7,
although I have no way to test it.

If you are trying to use ON TIMER and ON UEVENT in the same program,
it probably won't work. If you type in the following code and run it
you will find that once you press a key to CALL SetUevent, from that
time on when the TIMER(5) seconds passes, QB jumps first to the
section of code specified by ON TIMER(5) GOSUB, then IMMEDIATELY jumps
to the section of code specified by ON UEVENT GOSUB!

The only way I could find to stop this was to use UEVENT OFF at the
end of the user event section of code. If your user event is a one
shot deal then you can work around this way. Otherwise, if your user
event needs to stay enabled, you're probably out of luck. One other
possible work around is for your user event to include code to
determine if it is being called uselessly, then simply RETURN
immediately. Besides being a kluge, this still wastes time jumping to
the uevent code every time the TIMER goes off. A better work around is
to NOT use ON TIMER and simply keep track of ElapsedTime using your
own routines. This is fairly simple to do, and I have some code if
anyone would like to look at it.

I check into this BBS once in a while, or you can write me at:

Ray Crumrine
1800 Hilltop
Quincy, Il. 62301

Note that you must load the QB.QLB quick library to run this code.

DECLARE SUB SetUevent () 'In QB.QLB
ON TIMER(5) GOSUB TimerEvent
ON UEVENT GOSUB UserEvent
CLS
PRINT "Press any key to trigger the user event"
TIMER ON
UEVENT ON
DO
k$ = INKEY$
IF LEN(k$) THEN
IF k$ <> CHR$(27) THEN
CALL SetUevent
END IF
END IF
LOOP UNTIL k$ = CHR$(27)
END

TimerEvent:
PRINT "In the timer event section"
RETURN

The QBNews Page 33
Volume 2, Number 4 December 22, 1991


UserEvent:
PRINT "In the user event section"
'UEVENT OFF here will break the chain
RETURN

















































The QBNews Page 34
Volume 2, Number 4 December 22, 1991



----------------------------------------------------------------------
F u n a n d G a m e s
----------------------------------------------------------------------

Having a Ball by Charles Graham and David Cleary

Those of you who frequent the QuickBASIC echo on FIDONet should know
Charles Graham by now. Every so often he comes up with these little
entertaining programming problems. He sent one to me that I have
decided to include in The QBNews. I would like to have you send me
your solutions to this problem. As a little incentive, the first
person to correctly solves this problem will receive a free one year
disk subscription to The QBNews. You will also have your name
immortalized forever in the annals of QBNews history. So here it is and
Good Luck!

You have a balance scale and 12 balls that look and feel identical.
Eleven of the balls are the same weight. One of the balls is slightly
heavier or lighter -- you don't know which -- than the other 11. Using
only the balance scale and only for 3 weighings, you must determine
the odd ball, and whether it is lighter or heavier.

To help you get started, here is the code to set up your 12 balls:

'--------------------------------------------------------------------
'
DIM ball(12), attribute$(12) 'Set up 12 balls and attributes
RANDOMIZE TIMER 'Seed the RND function
'
begin: '
FOR x = 1 TO 12 'Give each ball an initial
ball(x) = 1 ' weight of 1 and an initial
attribute$(x) = " equal" ' attribute of "equal"
NEXT x '
oddball = INT(RND * 12) + 1 'Pick the odd ball randomly
IF RND < .5 THEN 'Pick the odd ball's weight:
oddweight = .9 ' a little less
ELSE ' or
oddweight = 1.1 ' a little more
END IF ' than the others
ball(oddball) = oddweight 'Give the odd ball its odd weight

That should be enough to get you started. The winner will be announced
in the next issue of The QBNews. Get your entries in soon!










The QBNews Page 35

DIAL.BAS

' DIAL    BAS : Dial a phone number on the screen 
' author .....: Dick Dennison [74270,3636] 1:272/34 914-374-3903 *hst 24 hrs
' supports ...: COM1 - COM4
' syntax .....: DIAL portnum%
' includes ...: None
' notes ......: Move the cursor with the arrow keys to the phone number
' : Press the ']' key and move the right arrow key across
' : the number and press Enter
' : Uses Basic's OPEN COMx commands
' cost .......: Free = Credit where credit due
' : Do not use as is for commercial use - may not be resold
' : May not be rebundled without prior written consent
' dated ......: 10/19/91
' credits ....: Thanks to Mike Welch for CLIPMSG, and Pete Petrakis for his
' : notes on Com Port swapping.

DECLARE SUB Hangup (Port%)
DECLARE SUB Getnum (row%, Col%, markit%, Port%)
DECLARE SUB Setup (Port%)

COLOR 0, 7
LOCATE 25, 1
PRINT " Move the cursor to the beginning of the phone number and press Space ";
LOCATE 10, 1
IF VAL(COMMAND$) < 1 OR VAL(COMMAND$) > 4 THEN 'Get the portnum%
PRINT "Port number must be on command line"
END
ELSE Port% = VAL(COMMAND$)
END IF

'Setup some special key functions
CR$ = CHR$(13)
Nul$ = CHR$(0)
ArrowLt$ = Nul$ + CHR$(75)
ArrowRt$ = Nul$ + CHR$(77)
ArrowUp$ = Nul$ + CHR$(72)
ArrowDn$ = Nul$ + CHR$(80)
EndKey$ = Nul$ + CHR$(79)
Esc$ = CHR$(27)
Home$ = Nul$ + CHR$(71)
SpaceBar$ = CHR$(32)
'Save vectors at bios Addresses for Com1-Com2
OldPort1H = PEEK(&H400)
OldPort1L = PEEK(&H401)
OldPort2H = PEEK(&H402)
OldPort2L = PEEK(&H403)

'Move cursor around
'==================================================================
DO 'This section lets the user move
In$ = INKEY$ 'move the cursor around on the screen
SELECT CASE In$ 'to the beginning of the phone number
CASE CR$
IF markit% THEN 'A CR signals the end of the highlight
row% = CSRLIN
Col% = POS(0) - count%
EXIT DO
END IF
CASE Esc$ 'END
END
CASE Home$ 'Goto the beginning of the line
LOCATE , 1
CASE EndKey$ 'Goto the end of the line
LOCATE , 80
CASE ArrowUp$ 'UpArrow
x% = CSRLIN
IF x% > 1 THEN LOCATE x% - 1
CASE ArrowDn$ 'DownArrow
x% = CSRLIN
IF x% < 25 THEN LOCATE x% + 1
CASE ArrowLt$ 'LeftArrow
IF POS(0) > 1 THEN LOCATE , POS(0) - 1
IF markit% THEN count% = count% - 1 'If markit% then ' ' was pressed
CASE ArrowRt$ 'RightArrow

IF markit% THEN
count% = count% + 1 'If markit% then ' ' was pressed
row% = CSRLIN: Col% = POS(0)
a% = SCREEN(row%, Col%)
PRINT CHR$(a%);
ELSE
IF POS(0) < 80 THEN LOCATE , POS(0) + 1
END IF
CASE SpaceBar$
IF markit% THEN
count% = count% + 1 'If markit% then ' ' was pressed
row% = CSRLIN: Col% = POS(0)
a% = SCREEN(row%, Col%)
PRINT CHR$(a%);
ELSE
BEEP
markit% = -1 'Flag set for marking number
END IF
END SELECT
LOCATE , , 1 'Keep cursor flashing
LOOP
'======================================================================

'Get the phone number off the screen
Getnum row%, Col%, count%, Port%

'Restore old vectors
CLOSE 1
DEF SEG = 0
POKE &H400, OldPort1H
POKE &H401, OldPort1L
POKE &H402, OldPort2H
POKE &H403, OldPort2L
DEF SEG
END

SUB Getnum (row%, Col%, markit%, Port%)
IF row% < 1 THEN row% = 1: IF Col% < 1 THEN Col% = 1
LOCATE row%, Col%
FOR x% = 0 TO markit% 'Read the phone number off the screen
a% = SCREEN(row%, Col% + x%)
Dialstr$ = Dialstr$ + CHR$(a%)
NEXT x%
LOCATE 23, 25
PRINT "Dialing : "; Dialstr$;
LOCATE 25, 1
PRINT " Pickup handset and then press space or ESC phone rings ";
COLOR 7, 0

Setup Port%
PRINT #1, "ATM1DT" + Dialstr$ 'Dial the numbar

DO
b$ = INKEY$
IF b$ = " " THEN
Hangup Port%
EXIT DO
END IF
IF b$ = CHR$(27) THEN
Hangup Port%
EXIT DO
END IF
LOOP

END SUB

SUB Hangup (Port%)

PRINT "...Disconnecting 1";
SELECT CASE Port% 'Drop DTR
CASE 1
OUT &H3FC, (INP(&H3FC) AND 252) 'com1
CASE 2
OUT &H2FC, (INP(&H2FC) AND 252) 'com2
CASE 3
OUT &H3FC, (INP(&H3FC) AND 252) 'com3
CASE 4
OUT &H2FC, (INP(&H2FC) AND 252) 'com4
END SELECT
PRINT "...2...";
PRINT #1, "+++"; 'Switch to modem command mode
SLEEP 1
PRINT #1, "ATH" 'Send hangup command
PRINT "...CLICK";

END SUB

SUB Setup (Port%)
'Sets up the comport by swapping the address fo com4 with com2 and
'com3 with com1 if necessary
DEF SEG = 0
POKE &H400, &HF8
POKE &H401, 3
POKE &H402, &HF8
POKE &H403, 2

SELECT CASE Port%
CASE 1
Start$ = "COM1:2400,N,8,1,DS0"
CASE 2
Start$ = "COM2:2400,N,8,1,DS0"
CASE 3
POKE &H400, &HE8 'For com1 to com3
POKE &H401, &H3
Start$ = "COM1:2400,N,8,1,DS0"
CASE 4
POKE &H402, &HE8 'For com2 to com4
POKE &H403, &H2
Start$ = "COM2:2400,N,8,1,DS0"
END SELECT
DEF SEG


OPEN Start$ FOR RANDOM AS 1

END SUB

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos from Google Play

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT