The QBNews Page 22 Volume 2, Number 3 September 15, 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 ---------------------------------------------------------------------- An Event-driven Mouse Support Libary by Tony Elliot The mouse is one of the easiest-to-use interface devices available for PCs. Even though it has been extremely popular over the last several years, Microsoft didn't provide direct support for it in the BASIC programming language until the recent release of Visual BASIC for Windows (although the BASIC Professional Development System [PDS] includes indirect support of the mouse through routines in the "User Interface Toolbox" included with that product). This doesn't mean that the mouse has been unreachable from BASIC programs. Mouse services can be easily accessed through BASIC's "CALL Interrupt" routine (as illustrated in a previous issue of QBNews). Also, most BASIC add-on developers include mouse routines written in assembly language as part of their regular complement of functions. So why the need for *another* set of mouse routines? Well, there are several reasons: 1. If you don't already own one, commercial add-on libraries can get rather expensive. If you are only interested in adding mouse support to your programs, you might have to shell out $100 to $200 (or more) for a product that consists of several hundred routines, only a few of which you'll use for this purpose. However, add-on libraries like MicroHelp's "Muscle" ($189) or Crescent's "QuickPak Professional" ($199) are definitely the way to go if you can afford it. Many of the other routines included in those products will no doubt be useful to you down the road. 2. Many of the public domain or "shareware" mouse support alternatives are written in BASIC, usually invoking BASIC's "CALL Interrupt" service. Although this is a completely functional approach, there is a substantial amount of overhead required (in terms of speed and code generation). CALL Interrupt is a relatively slow service to begin with, and the results returned by it must then be translated into a usable form. Doing all of this from BASIC may not allow you to poll the mouse as frequently as required by some applications, therefore you may occasionally miss some real-time events such as button-clicks. 3. Using routines written in assembly language for minor tasks such as mouse support is, in my humble opinion, the way to go. It adds a minimal amount of overhead to your BASIC programs and does the job as quickly as possible. This equates to smaller, faster, and from the users' point of view, more responsive programs. 4. The set of mouse routines described in this article has one important feature that I haven't seen elsewhere in DOS-based BASIC. Utilizing the "ON UEVENT" service available since QB 4.00a, they allow you to create "mouse event-driven" programs. That is, instead of constantly polling the mouse for activity, you can go about your normal business and when a programmer-defined type of mouse activity occurs (a button press, release, or mouse movement), your program is interrupted and control is transferred to a subroutine that you The QBNews Page 23 Volume 2, Number 3 September 15, 1991 specify. This allows you to logically group your mouse-specific code instead of having it sprinkled here and there throughout your program. The two mouse-programming paradigms, "polled" and "event-driven," each have advantages and disadvantages. We will discuss each in detail as the routines are being presented below. The file MOUSE.REF contains a reference listing each of the routines individually, along with the appropriate descriptions and parameter lists associated with them. The various code fragments used below each assume that the file MOUSE.BI is $Included at the top of your program. It contains the declarations and the TYPE..END TYPE structures required by these routines. Many of the code fragments also assume that you have already reset the mouse and the pointer is visible. Also keep in mind that we won't be talking too much about -how- the routines actually work. Fully commented assembly source code is provided for those enquiring minds that "what to know." An example program called MOUSE.BAS is also included. It demonstrates all of the mouse routines described in this article. ---------------------------------------------------------------------- Resetting the Mouse Driver ---------------------------------------------------------------------- When writing programs that will recognize and accept input from a mouse, the first step is to determine if a software "driver" for the mouse is installed. If one is found, it must be "reset" before our program can actually communicate with the mouse. Calls to any of the other mouse routines described here (besides the three "reset" routines) will simply be ignored if the mouse has not been reset or is not present at all. As a convenience, three routines are provided to determine the presence of the mouse and to reset it, if desired: MouseReset% - An integer function that checks to see if a mouse driver is installed, and if so, resets it. The number of buttons the mouse has is returned as a result, or zero if a mouse driver is not installed. When the mouse is reset, it is left in the following state: - Mouse pointer is at the center of the screen - Display page for mouse set to 0 - Mouse pointer is hidden (off) - Mouse pointer shape is the default arrow shape in the graphics video modes or a reverse block in the text modes. MouseAlreadyReset% - An integer function that checks to see if the mouse has already been reset by this program. If so, it returns the number of buttons the mouse has, otherwise it returns zero and takes no further action. This is handy in situations where you don't want to reset the mouse if it has been reset previously (doing so The QBNews Page 24 Volume 2, Number 3 September 15, 1991 unnecessarily takes a couple of seconds and has other, sometimes undesirable, effects such as those listed above). MouseInstalled% - An integer function that checks for the presence of a mouse without resetting it. Most of the time, MouseReset% will be all that you need. For example: NumberOfButtons% = MouseReset% IF NumberOfButtons% THEN PRINT NumberOfButtons%; " button mouse found and reset!" ELSE PRINT "Mouse not found." END IF Once the mouse has been successfully reset, you are then free to use any of the other functions. ---------------------------------------------------------------------- Obtaining Information About Mouse Hardware and Software ---------------------------------------------------------------------- If you wish to obtain specific information about the mouse hardware and software currently in use, the MouseGetInfo routine can be used to identify the version number of the mouse driver software, the type of mouse (bus, serial, InPort, PS/2, or HP), and the hardware interrupt line (IRQ) the mouse is sitting on. For example: CALL MouseGetInfo(MajorV%, MinorV%, MouseType%, Irq%) PRINT "Mouse software version:";MajorV% + MinorV% / 100 PRINT "Mouse type: "; SELECT CASE MouseType% CASE 1 PRINT "Bus" CASE 2 PRINT "Serial" CASE 3 PRINT "InPort" CASE 4 PRINT "PS/2" CASE 5 PRINT "HP" CASE ELSE PRINT "Beats the heck outta me! Type#";MouseType% END SELECT PRINT "Using IRQ";Irq% This information is useful in some cases, but generally there's no particular need for you to be aware of it. ---------------------------------------------------------------------- The Mouse Pointer ---------------------------------------------------------------------- The QBNews Page 25 Volume 2, Number 3 September 15, 1991 After the mouse has been reset, you'll want to make the mouse "pointer" (or "cursor") visible. The visibility state of the mouse pointer is controlled by the MousePointerOn and MousePointerOff routines. Remember, the MouseReset function initially turns the pointer OFF. For example: NumberOfButtons% = MouseReset% LINE INPUT "The mouse pointer is invisible. Press .. ",A$ CALL MousePointerOn LINE INPUT "The mouse pointer is visible. Press .. ",A$ CALL MousePointerOff PRINT "The mouse pointer is invisible again. Press .. " By default, the mouse driver uses a "software pointer" while in the text video modes. This means that a mouse pointer is visible on the screen because the mouse driver is altering the screen's color attribute and/or the character at the location where the pointer is to be placed. This makes that location stand out from the rest of the data displayed on the screen. The default software pointer simply reverses the color attributes at the pointer position. When the pointer is moved, it restores the original attribute and character at the current position, stores the original attribute and character for the new position, and then applies the defined masks to the new position. Before updating the screen using PRINT, CLS, etc., it's necessary to turn the mouse pointer off. If you were to overwrite the screen at the mouse pointer position while the pointer is visible, the mouse driver would not be aware of this occurrence. The next time the pointer is moved, the original attribute and character stored by the mouse driver would be restored to that location. The result is what I like to refer to as "mouse droppings." For this reason, it's generally considered good practice to leave the mouse pointer off except when your program is waiting for input from the user. The mouse driver provides a service whereby you can customize the effects that the pointer has on the color attribute and character at the pointer position. This is accomplished by providing two "bit-masks" that define which bits comprising the attribute/character will be preserved (ANDed) and which will then be toggled (XORed). Since manipulation of the mouse pointer on this level is of little interest to most programmers and would require a crash course in video memory and bit manipulation, I'll skip the nitty-gritty and describe the supplied routine. If you would like more in-depth information about this function, refer to the Microsoft Mouse Programmers Guide, or leave a message for me on Compuserve. I'll be happy to help. The routine MouseSetSoftwarePointer is used to define the "AND" and "XOR" bit masks for the software cursor. For example: AndMask% = &H77FF XorMask% = &H7700 CALL MouseSetSoftwarePointer(AndMask%, XorMask%) The QBNews Page 26 Volume 2, Number 3 September 15, 1991 The first byte of each mask represents the changes made to the color attribute. The "77" part of the AND mask indicates that the foreground color (bits 0-2) and background color (bits 4-6) will be preserved and the high- intensity (bit 3) and blink (bit 7) bits will be turned-off. The "77" part of the XOR mask indicates that the bits comprising the foreground and background colors should then be toggled. The "FF" part of the AND mask indicates that all bits comprising the character should be preserved. The "00" part of the XOR mask indicates that none of the character's bits will be toggled. In other words, when the mouse pointer appears on the screen, the color of the cell will be modified but the character won't. To take another example, we'll leave the color attribute alone, but will change the mouse pointer to an asterisk: AndMask% = &HFF00 'Preserve all attribute bits and discard the char XorMask% = &H002A 'Don't mess with attribute, make char an "*" CALL MouseSetSoftwarePointer(AndMask%, XorMask%) Another type of pointer available in text video modes is the "hardware pointer." It is called that because it is similar to the normal scan line-oriented text cursor that we're all familiar with. It appears on the screen as a blinking block. You can define it size, and to a degree, it's shape. Like the text cursor, the mouse hardware pointer is comprised of from 0 to 7 scan lines. When defining a hardware pointer, you specify the starting and ending scan line number, with 0 at the top of the character cell and 7 at the bottom, much like you would use BASIC's LOCATE statement to alter the shape of the text cursor. For example: 'A full "block" StartScan% = 0 EndScan% = 7 CALL MouseSetHardwarePointer(StartScan%, EndScan%) 'Or 'A thin line in the middle of the character cell StartScan% = 3 StartScan% = 4 CALL MouseSetHardwarePointer(StartScan%, EndScan%) As some of you may be aware, the actual height of a character cell varies between 8 to 16 scan lines depending on the display adapter and the video mode used. This function automatically translates the requested 0-7 range into the scan line range appropriate for the display adapter and video mode in use. ---------------------------------------------------------------------- What's the Mouse Up To? ---------------------------------------------------------------------- When working with the mouse, the big questions are "Where is the mouse pointer?" and "Is a button pressed or released?" This information is required to process the most rudimentary mouse action, the "click." The QBNews Page 27 Volume 2, Number 3 September 15, 1991 The routine MouseGetStatus can used to answer these questions for you. For example: REM $INCLUDE: 'MOUSE.BI' DECLARE FUNCTION Pressed$(Button%) 'Used only by this example NumberOfButtons% = MouseReset% IF NumberOfButtons% = 0 THEN PRINT "No mouse detected." END END IF CALL MousePointerOn CLS PRINT "Press any key to end:" DO CALL MouseGetStatus(Lb%, Rb%, Cb%, Row%, Column%) LOCATE 3,1 PRINT " Left Button :"; Pressed$(Lb%) PRINT " Right Button:"; Pressed$(Rb%) IF NumberOfButtons% > 2 THEN PRINT " Center Button:"; Pressed$(Cb%) END IF PRINT " Current Row:"; Row% PRINT "Current Column:"; Column% LOOP UNTIL LEN(INKEY$) CALL MousePointerOff END FUNCTION Pressed$(Button%) IF Button% THEN Pressed$ = "Pressed" ELSE Pressed$ = "Released" END IF END FUNCTION Again, the MouseGetStatus routine returns information about what is going on with the mouse -right now-. If your program was busy doing other things (e.g. "polling" the mouse infrequently) and the user clicked the left button quickly, you might not detect it. As you might have expected, there are contingencies for this situation as well. Let's take another situation. Say you are only interested in the mouse pointer position when a button is pressed or released, and you don't want to worry about polling the mouse frequently enough. The routines MousePressInfo and MouseReleaseInfo return the row and column position of the mouse pointer when the specified mouse button was *last* pressed or released, respectively. For example: The QBNews Page 28 Volume 2, Number 3 September 15, 1991 Button% = 0 '0=left, 1=right and 2=center IF MousePressInfo%(Button%, Row%, Column%) THEN 'Returns number of times the specified button was pressed ' since the last check PRINT "Left button last pressed at"; Row%; ","; Column% END IF Button% = 1 'Right button IF MouseReleaseInfo%(Button%, Row%, Column%) THEN 'Returns number of times the specified button was released ' since the last check PRINT "Right button last released at"; Row%; ","; Column% END IF Along the same lines, you can also determine the net physical distance that the mouse has moved. The routine MouseMovement returns the number of "Mickeys" (approximately 1/200th of an inch) the mouse has moved since the last time this routine was called. For example: CLS PRINT "Left button display distance and right button ends program" CALL MouseMovement(Rows%, Columns%) 'To zero the counter DO CALL MouseStatus(Lb%, Rb%, Cb%, Row%, Column%) IF Lb% THEN CALL MouseMovement(Rows%, Columns%) LOCATE 3,1 PRINT " Mickeys moved (vertically):"; Rows% PRINT "Mickeys moved (horizontally):"; Columns% CALL MouseWaitForRelease END IF LOOP UNTIL Rb% If you were paying attention, then you noticed that I snuck a new routine into the above listing. MouseWaitForRelease suspends the program until all mouse buttons have been released. It's very handy for mouse-specific program "flow control." ---------------------------------------------------------------------- Writing Mouse Event-driven Programs ---------------------------------------------------------------------- The various methods of gathering mouse-related information we've discussed so far require you to poll the mouse driver periodically to see if a specific event has occurred (mouse click, etc.). Wouldn't it be much easier if the mouse driver could simply interrupt your program anytime it needs attention and then transfer control to a specified subroutine? This way, you wouldn't have to be concerned about polling the mouse periodically. In QuickBASIC 4.00a (distributed with BASIC 6.x), 4.00b (bug fix update to 4.00 and 4.00a), 4.50, and QBX (distributed with PDS) there The QBNews Page 29 Volume 2, Number 3 September 15, 1991 exists a little-known and seldom-used statement called "ON UEVENT GOSUB LineLabel." It's much like other BASIC event processing statements like "ON TIMER", "ON KEY", "ON COM", etc. in that it allows program control to be transferred to a subroutine when a special event occurs. A "UEVENT" or "User Event" is programmer-definable. In this case, we establish a "hook" into the mouse driver and when a specified type of mouse event occurs, the mouse driver notifies our assembly routine, which in turn notifies BASIC. The next time the BASIC runtime code processes events (at each line label or after each statement, depending on the use of the /V and /W compiler switches) control is transferred to the desired subroutine. Neat, huh? The routine MouseSetEvent is used to define the type of event(s) that you are interested in. These events can include any combination of a specific button being pressed or released, or any mouse movement at all. Once an event has occurred and program control has been transferred to your BASIC subroutine, the MouseGetEventInfo routine is used to determine exactly what type event occurred and where the mouse pointer was located at the time. When you wish to turn off the mouse event trap, call the MouseCancelEvent routine. Here's how it's done: REM $INCLUDE: 'MOUSE.BI' Buttons% = MouseReset% IF Buttons% = 0 THEN PRINT "Mouse not present." END END IF CALL MousePointerOn 'First, define the types of event(s) you want to trap. This is ' done by passing value in EventMask%. Just pick the events you ' wish to trap, add up their values and pass it to the ' MouseSetEvent routine. ' 1 = Any mouse movement ' 2 = Left button pressed ' 4 = Left button released ' 8 = Right button pressed ' 16 = Right button released ' 32 = Center button pressed ' 64 = Center button released 'Let's trap a left or right button release. That's 4 + 16 = 20. EventMask% = 4 + 16 CALL MouseSetEvent(EventMask%) 'Next, tell BASIC to watch for the event and to transfer program ' control to the at the MouseEvent line label when an event ' occurs. The QBNews Page 30 Volume 2, Number 3 September 15, 1991 ON UEVENT GOSUB MouseEvent 'Watch for the event UEVENT ON 'Enable UEVENT checking 'Let's set up an idle loop to demonstrate the event processing CLS PRINT "Each time you release the left or right mouse button, our" PRINT "event-handling code will be called. When you've seen" PRINT "enough, press any key to end this program." PRINT DO LOOP UNTIL LEN(INKEY$) 'Loop until a key press is detected 'To turn off event checking UEVENT OFF CALL MouseCancelEvent CALL MousePointerOff END MouseEvent: 'Transfer control here on a mouse event CALL MouseGetEventInfo(EventType%, Lb%, Rb%, Cb%, Row%, Column%) PRINT "Mouse event occurred: - "; '"AND" EventType% with the value of the event being checked IF EventType% AND 4 THEN PRINT "Left Button was released." END IF IF EventType% AND 16 THEN PRINT "Right Button was released." END IF PRINT "Mouse location:"; Row%; ","; Column% RETURN Using BASIC's event-trapping services generates larger executable programs because of the additional event-checking code required at runtime. However, there are ways to reduce this added overhead to a minimum. 1. Use "UEVENT ON" and "UEVENT OFF" around code in which you wish to have event-trapping active. Putting a "UEVENT ON" at the top of your program without a following UEVENT OFF will cause BASIC to generate event-checking code for your entire program. If you selectively use UEVENT ON and UEVENT OFF (e.g. around your input or selection code), - you- can keep the overhead to a minimum. 2. Programs that use BASIC event-traps must be compiled with the /V or /W switches. /V directs BASIC to check for an events after *every BASIC statement* (within the UEVENT ON and UEVENT OFF boundaries, that is). /W directs BASIC to check for events only when crossing line labels. The latter will obviously generate less overhead, but requires you to be more careful with your placement of line labels - no line labels, no event-checking. The QBNews Page 31 Volume 2, Number 3 September 15, 1991 ---------------------------------------------------------------------- Fine-tuning the Mouse ---------------------------------------------------------------------- In addition to controlling the mouse's visual characteristics, you can control its physical characteristics as well. This includes its sensitivity and double-speed threshold. "Sensitivity" is the ratio between the distance the mouse is physically moved (in Mickeys) and the distance the mouse pointer actually moves (in pixels) on the screen. The default ratios are one Mickey per pixel horizontally and two Mickeys per pixel vertically. The higher the ratio, the more physical mouse movement is required to move the mouse pointer. The routine MouseSetRatio is used to adjust the mouse sensitivity. For example: VertRatio% = 2 HorizRatio% = 1 CALL MouseSetRatio(VertRatio%, HorizRatio%) The "double-speed ratio" is the physical speed (in Mickeys per second) that the mouse must travel before the mouse pointer shifts into overdrive and moves across the screen twice as fast. The default is 64 Mickeys per second (about 1/3 of an inch per second). The routine MouseSetDblSpd is used to adjust the double-speed ratio. For example: MickeysPerSecond% = 200 'Set to one inch per second CALL MouseSetDblSpd(MickeysPerSecond%) Using the MouseGetSensitivity routine, you can also retrieve the current sensitivity settings from the mouse driver. For example: CALL MouseGetSensitivity(VertRatio%, HorizRatio%, _ MickeysPerSecond%) ---------------------------------------------------------------------- Miscellaneous Mouse Routines ---------------------------------------------------------------------- The following list of "miscellaneous" mouse routines can be useful from time-to-time. They allow you to restrict the mouse movement, position the mouse pointer through software, save and restore the mouse "state," allow you to define the coordinate system the mouse pointer location is returned in (character-based row/column format or X/Y pixel-based format), and more. MouseSetWindow - Used to define a rectangular area on the screen in which the mouse pointer movement will be restricted. Once called, the user will not be able to move the mouse pointer out of the defined area. To disable a previously defined window, call this routine again with the dimensions of the entire screen. For example: The QBNews Page 32 Volume 2, Number 3 September 15, 1991 TopRow% = 6 LeftColumn% = 10 BottomRow% = 20 RightColumn% = 70 CALL MouseSetWindow(TopRow%, LeftColumn%, BottomRow%,_ RightColumn%) 'To turn the window off (assuming an 80 X 25 screen) TopRow% = 1 LeftColumn% = 1 BottomRow% = 25 RightColumn% = 80 CALL MouseSetWindow(TopRow%, LeftColumn%, BottomRow%,_ RightColumn%) MouseSetExclusionArea - When the mouse pointer is moved into the rectangular area of the screen defined by this routine, it is made invisible. Use the MousePointerOn routine to make the pointer visible again. For example: TopRow% = 6 LeftColumn% = 10 BottomRow% = 20 RightColumn% = 70 CALL MouseSetExclusionArea(TopRow%, LeftColumn%, BottomRow%,_ RightColumn%) 'To turn the mouse pointer back on: CALL MousePointerOn MouseSetPointer - Used to position the mouse pointer on the screen. For example: Row% = 5 Column% = 20 CALL MouseSetPointer(Row%, Column%) MousePixelsOn - Used to change the default coordinate system used by the mouse routines discussed in this article from character-based row/column coordinates (which is the default) to X/Y pixel-based coordinates. This routine defines the coordinate system used for both input -and- output. This routine is provided because row/column coordinates are generally preferred in text video modes and X/Y pixel- based coordinates are generally preferred in graphics video modes. Use the MousePixelsOff routine to switch back to row/column coordinates. For example: CALL MousePixelsOn 'Switch to X/Y pixel-based coordinates CALL MousePixelsOff 'Switch back to row/column format MouseSaveState - Saves the current mouse "state." This includes the current position, visibility, shape, and other mouse pointer characteristics. You could use this routine just prior to a SHELL so the original state could be easily restored on returning to your The QBNews Page 33 Volume 2, Number 3 September 15, 1991 program. This routine automatically allocates the required amount of memory from DOS and releases it on the call the MouseRestoreState. The MouseSaveState routine can be declared as a SUB or an integer FUNCTION. If declared as a FUNCTION, it returns -1 (TRUE) if the state was successfully saved and 0 (FALSE) if insufficient memory was available. We have it DECLAREd as a FUNCTION in the MOUSE.BI file included with this article. For example: Success% = MouseSaveState% IF Success% THEN PRINT "Mouse state saved!" CALL MouseRestoreState ELSE PRINT "Insufficient memory to save mouse state!" END IF MouseSetPage - Sets the video display page on which the mouse pointer will be visible. Normally, the mouse driver is aware of changes in active video pages, so it is not usually necessary to call this routine. However, if you are using non-standard methods of switching video pages, this is how you can tell the mouse driver what you are up to. The function MouseGetPage% is used to return what the mouse driver believes to be the active video page number current in use. For example: SCREEN ,,1,1 'Switch BASIC into page 1 PageNum% = 1 CALL MouseSetPage(PageNum%) 'Tell the mouse driver about it PRINT "Mouse pointer on page"; PRINT MouseGetPage% MouseExit - Releases all memory allocated by these mouse routines and unhooks the "user mouse service" function required by MouseSetEvent. You need to call this routine ONLY if: You are about to CHAIN to another program and the MOUSE.OBJ is *not* in a BASIC 6.X or PDS extended runtime library, or You are about to SHELL and the MOUSE.OBJ file -is- in an extended runtime library. If your BASIC program terminates normally (with an "END" or "SYSTEM" statement), there is no need to call MouseExit at all. ---------------------------------------------------------------------- Special Considerations ---------------------------------------------------------------------- If your BASIC program terminates via the "END" or "SYSTEM" statements, we automatically release memory that we've allocated and "unhook" the mouse driver. Using a service called "B_OnExit", the BASIC runtime code calls our internal clean-up code just prior to returning system control back to DOS. The QBNews Page 34 Volume 2, Number 3 September 15, 1991 However, if you CHAIN to another program, the CHAINing program actually terminates but BASIC does not process the B_OnExit chain allowing us to clean up our mess. Essentially this means that your system can potentially lock up if you've used the MouseSetEvent or the MouseSaveState routines. See the entry for MouseExit above for more information. ---------------------------------------------------------------------- Using the Routines ---------------------------------------------------------------------- Now since we've gotten all of that technical stuff out of the way, you're probably about ready to actually being using the routines. Included with this article, you find a file called MOUSE.OBJ. It is an object file which contains all of the mouse routines described here. You can put this .OBJ file in a QuickLibrary, a LINK library, or LINK it directly to your compiled programs. It's completely compatible with QuickBASIC versions 4.00a - 4.50, BASIC 6.X, and PDS 7.x. Distributed with this article is a batch program called BLDLIB.BAT. It will automatically build a QuickLibrary (.QLB) and LINK library (.LIB) for your compiler version. Type "BLDLIB" at the DOS prompt for instructions. However, if you prefer to know about the "hows" and "whys" and would like to build your libraries manually, read on. To build a QuickLibrary so you can call the mouse routines from with the QB(x) development environments, issue the following command at the DOS prompt: LINK /Q MOUSE, MOUSE.QLB, NUL, BQLB45; The above LINK command will generate a QuickLibrary for QuickBASIC version 4.50. Change the QuickLibrary support module name (listed above as BQLB45) to BQLB41 or QBXQLB to build a QuickLibrary for QuickBASIC 4.00x and QBX, respectively. To build a LINK library or to add the MOUSE.OBJ file to an existing LINK library, type the following at the DOS prompt: LIB MOUSE +MOUSE.OBJ ; The above LIB command will create a library called MOUSE.LIB. If you wish to add the mouse routines to an existing library, substitute that library name for the first occurrence of MOUSE above. To load the example program into the environment, type the following at the DOS prompt: QB MOUSETST /L MOUSE For QB 4.X QBX MOUSETST /L MOUSE For QBX The above commands load the example program MOUSETST.BAS into the environment along with the MOUSE.QLB QuickLibrary we created earlier. The QBNews Page 35 Volume 2, Number 3 September 15, 1991 To compile and LINK your program, you can select "Make an EXE" from the QB(x) environments (assuming you have constructed matching .QLB and .LIB files per the above instructions), or you can manually compile and link from the DOS prompt. The latter is the preferred method because it gives you more control over compiler switches used. For example: BC MOUSETST /O/W ; LINK /EX MOUSETST + MOUSE ; Or, if you've placed MOUSE.OBJ in a link library, you can do this: LINK /EX MOUSETST,,NUL, LibraryName ; [EDITOR'S NOTE] All files for this article can be found in the file MOUSE.ZIP. ********************************************************************* Tony Elliott has been programming in BASIC and assembly langauge for over ten years. He worked for MicroHelp, Inc. for almost four years as their Technical Support Manager and was directly involved in the development of many of their QuickBASIC/PDS products. He has spoken at a number of BASIC Symposiums conducted around the country between 1989 - 1991, and was a speaker at the August, 1991 Microsoft Developers Tools Forum held in Seattle. He has written articles for BASICPro Magazine and MicroHelp's "BASIC Users Group" Newsletter. He is now Vice President of EllTech Development, Inc. which recently developed and is currently marketing a network-ready replacement for PDS ISAM called "E-Tree Plus" (see the ad included in this issue). **********************************************************************