home *** CD-ROM | disk | FTP | other *** search
Text File | 1991-12-22 | 101.0 KB | 2,150 lines |
- 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
-
-