The Pascal Wizard's Library =-------------------------= Version 2.1 PasWiz (C) Copyright 1996 Charon Software, All Rights Reserved This is PasWiz, a library of assorted routines for use with Turbo Pascal, Quick Pascal, and compatible compilers. The PasWiz collection is copyrighted, but may be distributed as long as the following conditions are met: All PasWiz files must be distributed together as a unit in unmodified form. No files may be left out or added. YOU USE THIS LIBRARY AT YOUR OWN RISK. It has been tested by me on my own computer, but I will not assume any responsibility for any problems which PasWiz may cause you. Shareware operates on a "try before you buy" basis. It is expected that if you find PasWiz useful, you will register your copy. You may not use PasWiz routines in programs intended for distribution unless you have registered. Registration entitles you to receive full source code for PasWiz and a great many other products. See the ORDER.FRM file for details. Table of Contents page 2 Overview and Legal Info .................................... 1 Archive Directories ........................................ 3 BCD Math ................................................... 5 Equipment Info ............................................. 9 Expression Evaluator ...................................... 12 Extensions to Pascal's Math ............................... 13 Joystick Support .......................................... 15 Keyboard Control .......................................... 16 String Stuff .............................................. 20 Music ..................................................... 24 Mouse ..................................................... 25 Archive Directories page 3 Unit: Archives When I started in the microcomputer industry, there was a small variety of file archivers, all (more or less) compatible. They did not provide compression, which was relegated to another large selection of more-or-less compatible utilities. Then came SEA's ARC. It was very slow, but it did compression as well as archiving, and included CRC checks so you could know whether the files were intact. It swept the BBS scene in short order, becoming the new standard. A few other archivers competed on about a level footing, providing only minor variances on the ARC theme. Then SEA decided to sue one of their more successful competitors, Phil Katz (PKARC). The end result was PKZIP, which totally blew ARC away-- a useful hint to companies that choose to litigate instead of innovate. In the chaos resulting from the breaking of the former ARC standard, many other archivers came into being: ARJ, LZH, PAK, ZOO, et al. PBWiz helps resolve the confusion by providing a single set of routines which allow you to view the contents of archives in any of the above-mentioned formats: ARJ, LZH, PAK, ZIP, ZOO, and even the antique ARC protocol. It also handles self-extracting EXE files of the forms produced by ARJ, LHARC and ZIP2EXE. Only archive directories are provided at this time. Other formats will also be added as they arise. If you have details on the format of an archive that you'd like me to add to PBWiz, please send them my way. Viewing archive directories is handled in roughly the same fashion as you might view a DOS file directory. This makes it possible to treat an archive and a subdirectory in a similar manner, as long as the archive doesn't contain archives itself. When you're looking for the first file in an archive, use the FindFirstA function. You must specify the archive name and a file name. The archive name may include a drive and path specification, and does not need to have the archive extension. If you leave off the extension, FindFirstA will use the first archive it comes across that matches the rest of the specification. Note that the archive specification may not contain wildcards. In contrast, the search file name may not contain drive or path specs, but may contain wildcards. PROCEDURE FindFirstA (ArchiveName, FileName: String; VAR ErrCode: Integer); Archive Directories page 4 Unit: Archives If there are no files to be found, or if the archive specification was bad, an error code will be returned. If there was no error, there may well be more files to be found. You can find each of them with FindNextA: FindNextA (VAR ErrCode: Integer); Of course, just finding a matching file doesn't do you much good unless you can retrieve information about it. You can use any of the following routines to provide information about a matched file: FUNCTION GetNameA: String; { file name } FUNCTION GetDateA: String; { file date } FUNCTION GetTimeA: String; { file time (24-hour) } FUNCTION GetCRCA: String; { file CRC (8-char hexadecimal) } FUNCTION GetStoreA: String; { file storage method } PROCEDURE GetSizeA (VAR OriginalSize: LongInt; VAR CurrentSize: LongInt); When you're done viewing an archive, be sure to close it: PROCEDURE CloseA; Let's try an example, to view all files in an archive: USES Archives; VAR ErrCode: Integer; BEGIN IF ParamCount = 1 THEN BEGIN FindFirstA(ParamStr(1), '*.*', ErrCode) WHILE ErrCode = 0 DO BEGIN WriteLn(GetNameA); FindNextA(ErrCode); END; CloseA; END ELSE WriteLn('Archive name expected'); END. BCD Math page 5 Unit: BCD Some of you may not have heard of BCD math, or at least not have more than a passing acquaintance with the subject. BCD (short for Binary-Coded Decimal) is a way of encoding numbers. It differs from the normal method of handling numbers in several respects. On the down side, BCD math is much slower than normal math and the numbers take up more memory. However, the benefits may far outweigh these disadvantages, depending on your application: BCD math is absolutely precise within your desired specifications, and you can make a BCD number as large as you need. If your applications don't require great range or precision out of numbers, normal Pascal math is probably the best choice. For scientific applications, accounting, engineering and other demanding tasks, though, BCD may be just the thing you need. The BCD math routines provided by PasWiz allow numbers of up to 255 digits long (the sign counts as a digit, but the decimal point doesn't). You may set the decimal point to any position you like, as long as there is at least one digit position to the left of the decimal. Since Pascal doesn't support BCD numbers directly, we store the BCD numbers in strings. The results are not in text format and won't mean much if displayed. A conversion routine allows you to change a BCD number to a text string in any of a variety of formats. Note that the BCD math handler doesn't yet track overflow/underflow error conditions. If you anticipate that this may be a problem, it would be a good idea to screen your input or to make the BCD range large enough to avoid these errors. Let's start off by considering the BCD range. This is kept in two integer variables, LDigits and RDigits, which can be accessed by your program. These variables specify the maximum number of digits to the left and to the right of the decimal point. There must be at least one digit on the left, and the total number of digits must be less than 255. The BCD strings will have a length that's one larger than the total number of digits, to account for the sign of the number. The decimal point is implicit and doesn't take up any extra space. It is assumed that you will only use one size of BCD number in your program-- there are no provisions for handling mixed-length BCD numbers. Of course, you could manage that yourself with a little extra work, if it seems like a useful capability. If you don't change LDigits or RDigits, the default size of the BCD numbers will be 32 (20 to the left, 11 to the right, 1 for the sign). BCD Math page 6 Unit: BCD Before doing any BCD calculations, you must have some BCD numbers! The BCDSet routine takes a number in text string form and converts it to BCD: TextSt := '1234567890.50'; Nr := BCDSet(TextSt); { FUNCTION BCDSet (TextSt: String): String; } If your numbers are stored as actual numbers, you can convert them to a text string with Pascal's Str procedure, then to BCD: Str(Number, TextSt); Nr := BCDSet(TextSt); BCD numbers can also be converted back to text strings, of course. You may specify how many digits to the right of the decimal to keep (the number will be truncated, not rounded). If the RightDigits value is positive, trailing zeros will be kept; if negative, trailing zeros will be removed. There are also various formatting options which may be used. Here's how it works: FUNCTION BCDFormat (Nr: String; HowToFormat, RightDigits: Integer): String; The HowToFormat value may be any combination of the following (just add the numbers of the desired formats together): 0 plain number 1 use commas to separate thousands, etc 2 start number with a dollar sign 4 put the sign on the right instead of the left side 8 use plus sign instead of space if nr is not negative The BCD math functions are pretty much self-explanatory, so I'll keep the descriptions brief. The single-parameter functions are listed on the next page. BCD Math page 7 Unit: BCD { absolute value } FUNCTION BCDAbs (Nr: String): String; { cosine } FUNCTION BCDCos (Nr: String): String; { cotangent } FUNCTION BCDCot (Nr: String): String; { cosecant } FUNCTION BCDCsc (Nr: String): String; { convert degrees to radians } FUNCTION BCDDeg2Rad (Nr: String): String; { the constant "e" } FUNCTION BCDe: String; { factorial } FUNCTION BCDFact (Nr: Integer): String; { fractional part of number } FUNCTION BCDFrac (Nr: String): String; { integer part of number } FUNCTION BCDInt (Nr: String): String; { negation } FUNCTION BCDNeg (Nr: String): String; { the constant "pi" } FUNCTION BCDpi: String; { convert radians to degrees } FUNCTION BCDRad2Deg (Nr: String): String; { secant } FUNCTION BCDSec (Nr: String): String; { signum } FUNCTION BCDSgn (Nr: String): Integer; { sine } FUNCTION BCDSin (Nr: String): String; { square root } FUNCTION BCDSqrt (Nr: String): String; { tangent } FUNCTION BCDTan (Nr: String): String; BCD Math page 8 Unit: BCD Notes on the single-parameter functions: The signum function returns an integer based on the sign of the BCD number: -1 if the BCD number is negative 0 if the BCD number is zero 1 if the BCD number is positive BCDpi is accurate to the maximum level afforded by the BCD functions. BCDe is accurate to as many as 115 decimal places. The actual accuracy, of course, depends on the size of BCD numbers you've chosen. The trigonometric functions (cos, sin, tan, sec, csc, cot) expect angles in radians. BCDDeg2Rad and BCDRad2Deg will allow you to convert back and forth between radians and degrees. Here is a list of the two-parameter functions: { addition } FUNCTION BCDAdd (Nr1, Nr2: String): String; { divide 1st by 2nd } FUNCTION BCDDiv (Nr1, Nr2: String): String; { multiplication } FUNCTION BCDMul (Nr1, Nr2: String): String; { subtract 2nd from 1st } FUNCTION BCDSub (Nr1, Nr2: String): String; { raise to a power } FUNCTION BCDPower (Nr: String; Power: Integer): String; { compare two numbers } FUNCTION BCDCompare (Nr1, Nr2: String): Integer; The comparison function returns an integer which reflects how the two numbers compare to each other: -1 Nr1 < Nr2 0 Nr1 = Nr2 1 Nr1 > Nr2 Equipment Info page 9 Unit: Equipment The equipment unit gives you information about the computing environment. This includes both installed software and hardware. The first function allows you to determine if an "enhanced" keyboard (101-key) is installed. It may not be able to figure out what the keyboard is on some older not-quite-clone PCs, in which case it will take the safe way out and report that there is no enhanced keyboard. This function returns -1 if there is an enhanced keyboard present, 0 if not. FUNCTION EnhKbd: Integer; Want to know the type of processor (CPU) being used? Can do! FUNCTION Processor: Integer; The results will be reported as a number which can be decoded as follows: 0 NEC V20 1 8088 or 8086 2 80186 3 80286 4 80386 5 80486 Maybe you'd like to check for a CD-ROM drive: FUNCTION CDROM: Integer; This tells you how many logical drives exist, if there is a CD-ROM available. If not, it will return 0. Note that the CD-ROM installation check conflicts with the GRAPHICS.COM installation check for DOS 4.0, due to some screw-up at IBM or Microsoft. I'm not yet sure whether DOS 5.0 is similarly afflicted. The number of floppy drives installed is retrieved with this: FUNCTION Floppies: Integer; To get the number of serial (COM) ports and parallel (LPT) ports, use the following functions: FUNCTION CommPorts: Integer; FUNCTION PrtPorts: Integer; Equipment Info page 10 Unit: Equipment Now, there may be up to four floppy drives in a system; however, the AT CMOS data area only directly supports two. This makes it easy to find out what kind of drives the first two are, but not the second two, if any. Such is life. PROCEDURE FloppyType (VAR Drive1, Drive2: Integer); The results from FloppyType are returned as follows: 0 no drive 1 5 1/4" 360K 2 5 1/4" 1.2M 3 3 1/2" 720K 4 3 1/2" 1.44M 5 3 1/2" 2.88M Result codes of 6-7 are also available, but are not yet defined. New memory types sure have burgeoned over the years... expanded, extended, and now XMS. There are routines to check all of these: { amount of extended memory installed } FUNCTION AllExtMem: LongInt; { BIOS extended memory available } FUNCTION GetExtM: LongInt { amount of expanded memory (a page is 16 Kbytes) } PROCEDURE GetEMSm (VAR TotalPages, FreePages: Integer); { amount of XMS memory (returned in kilobytes) } PROCEDURE GetXMSm (VAR LargestFreeBlock, TotalFree: LongInt); When you're dealing with extended memory, whether it be BIOS-type or using the XMS standard, the results are returned in kilobytes. Multiply 'em by 1024 to convert to bytes. When you're dealing with expanded memory (EMS), the results are in pages of 16,384 bytes. Equipment Info page 11 Unit: Equipment A few more routines to get the versions of the EMS and XMS drivers, if any: PROCEDURE GetEMSv (VAR MajorV, MinorV: Integer); PROCEDURE GetXMSv (VAR MajorV, MinorV: Integer); These return the major and minor version numbers as two separate integers. For example, EMS 4.0 would return major version 4, minor version 0. It's nice to know a little about the operating environment. With the below routines, you can find out what the DOS version is; what version of 4DOS, if any, is in use; and whether Microsoft Windows is running. PROCEDURE GetDOSv (VAR MajorV, MinorV: Integer); PROCEDURE Get4DOSv (VAR MajorV, MinorV: Integer); PROCEDURE WinCheck (VAR MajorV, MinorV: Integer); These return results as major and minor version numbers, as discussed above. The Get4DOSv and WinCheck routines return zeroes if 4DOS and Windows, respectively, are not in use. There are a couple of curious features of GetDOSv to keep in mind. If the version is 10 or higher, you're running under OS/2. DOS version 10 is actually OS/2 1.0, version 20 is OS/2 2.0, and so on. Secondly, if you're using DOS 5.0, the version reported may not be 5.0-- DOS 5.0 can be told to reply with a lower version number to allow badly written older software to run properly. One final routine that should be of some value is the one that allows you to find out what kind of display is available. It tells you the specific adapter and whether the display is color or monochrome. There is one case in which it can be confused, however-- if the adapter is CGA, the display is assumed to be color, since there is no way for the computer to know any differently. So, although this routine provides a good idea of what is available, it would be a good idea to provide an option to tell the program that a monochrome display is attached. Microsoft normally uses "/B" for this purpose, so that might be a good standard to stick with. PROCEDURE GetDisplay (VAR Adapter: Integer; VAR Mono: Boolean); The adapter will be one of the following: 1 MDA 4 EGA 2 Hercules 5 MCGA 3 CGA 6 VGA Expression Evaluator page 12 Unit: ExtMath The expression evaluator solves numeric equations. It allows you to find the result of an expression contained in a string. Normal algebraic precedence is used, e.g. 4+3*5 evaluates to 19. The usual numeric operators (*, /, +, -, ^) are supported (multiply, divide, add, subtract, and raise to a power). Negation is also supported. Parentheses can be used for overriding the default order of operations. You may use either double asterisk ("**") or caret ("^") symbols to indicate exponentiation. The constant PI is recognized, as are the following functions: ABS absolute value INT integer ACOS inverse cosine LOG natural log ASIN inverse sine SIN sine ATAN inverse tangent SQRT square root COS cosine TAN tangent FRAC fraction Functions should not be nested. Trig functions expect angles in radians. To evaluate an expression, you pass it to the evaluator as a string. You will get back either an error code or a real result. See the CALC.PAS program for a working example. PROCEDURE Evaluate (Expression: String; VAR Result: Real; VAR ErrCode: Integer); An expression evaluator adds convenience to any program that needs to accept numbers. Why make someone reach for a calculator when number crunching is what a computer does best? Extensions to Pascal's Math page 13 Unit: ExtMath For the most part, the math routines in this library are designed to provide alternatives to the math routines that come with Pascal. Still, Pascal's own math support is quite adequate for many purposes, so there's no sense in ignoring it. Here are some functions which improve on Pascal's math. { inverse cosine } FUNCTION ArcCos (Number: Real): Real; { 1.0 >= Number >= -1.0 } { inverse hyperbolic cosine } FUNCTION ArcCosH (Number: Real): Real; { inverse cotangent } FUNCTION ArcCot (Number: Real): Real; { inverse hyperbolic cotangent } FUNCTION ArcCotH (Number: Real): Real; { inverse cosecant } FUNCTION ArcCsc (Number: Real): Real; { inverse hyperbolic cosecant } FUNCTION ArcCscH (Number: Real): Real; { inverse secant } FUNCTION ArcSec (Number: Real): Real; { inverse hyperbolic secant } FUNCTION ArcSecH (Number: Real): Real; { inverse sine } FUNCTION ArcSin (Number: Real): Real; { 1.0 >= Number >= -1.0 } { inverse hyperbolic sine } FUNCTION ArcSinH (Number: Real): Real; { inverse hyperbolic tangent } FUNCTION ArcTanH (Number: Real): Real; { ceiling: smallest integer >= specified number } FUNCTION Ceil (Number: Real): Real; { hyperbolic cosine } FUNCTION CosH (Number: Real): Real; { cotangent } FUNCTION Cot (Number: Real): Real; Extensions to Pascal's Math page 14 Unit: ExtMath { cosecant } FUNCTION Csc (Number: Real): Real; { convert degrees to radians } FUNCTION Deg2Rad (Number: Real): Real; { the constant "e" } FUNCTION e: Real; { error function } FUNCTION Erf (Number: Real): Real; { factorial } FUNCTION Fact (Number: Integer): Real; { floor: largest integer <= specified number } FUNCTION Floor (Number: Real): Real; { log (base 10) } FUNCTION Log (Number: Real): Real; { convert radians to degrees } FUNCTION Rad2Deg (Number: Real): Real; { raise a number to a power } FUNCTION Raise (Number: Real; Power: Integer): Real; { secant } FUNCTION Sec (Number: Real): Real; { hyperbolic secant } FUNCTION SecH (Number: Real): Real; { signum (integer) } FUNCTION SgnI (Number: Integer): Integer); { signum (real) } FUNCTION SgnR (Number: Real): Integer); { hyperbolic sine } FUNCTION SinH (Number: Real): Real; { tangent } FUNCTION Tan (Number: Real): Real; { hyperbolic tangent } FUNCTION TanH (Number: Real): Real; Joystick Support page 15 Unit: Joystick There's little enough to say about the joystick. A PC may have up to two of them. Normally, a joystick has two buttons, and returns a pair of coordinates which describe the position in which it is being held. The coordinates vary depending on the individual joystick, the computer involved, and other factors, so it is wise to provide a calibration routine to customize your program for the joystick(s) involved. The FlightStick joystick has a dial for throttle control, which is available only in a one-joystick setup. The throttle value is returned as the Y coordinate of an imaginary second joystick. Coordinates (X, Y), where X is the horizontal and Y the vertical coordinate, range from around 0 to around 150 on my FlightStick. Since these values may vary significantly, however, they are returned as words. The state of the joystick buttons may be checked individually or all at once. If you need to know the state of multiple buttons, it is faster to do it all at once, but you may do it either way. FUNCTION ButtonA1: Boolean; { button 1 on 1st joystick } FUNCTION ButtonA2: Boolean; { button 2 on 1st joystick } FUNCTION ButtonB1: Boolean; { button 1 on 2nd joystick } FUNCTION ButtonB2: Boolean; { button 2 on 2nd joystick } PROCEDURE Buttons (VAR A1, A2, B1, B2: Boolean); { all buttons } The joystick positions are handled internally by a rather unfortunate method involving a timer, due to the way IBM designed the joystick interface. This makes getting the position rather slow. To alleviate the problem, all positions are returned at once. PROCEDURE Positions (VAR AX, AY, BX, BY: Word); Joystick support is handled through a set of BIOS routines which were added to the PC around 1985. These routines are not present in some of the oldest PCs. Keyboard Control page 16 Unit: Keyboard The keyboard is not a particularly exciting or glamorous device. In fact, we tend to forget about it except when it gets in the way. Sometimes it's a hardware problem-- squishy or clacking keys, or perhaps a commonly-used key placed in an out-of-the-way location. Then again, sometimes it's the software that's the problem. There are many aspects of keyboard control, not all of which are necessarily related to input. This unit will let you handle the keyboard in ways you may not have realized were possible. Better yet, it can help make keyboard control easier than the users of your programs dreamed possible. Let's start out with keyboard output. Yep, not input-- output. We can stuff up to 15 keys into the keyboard buffer. Why would we ever want to do this? Perhaps to allow your program to pop-up a TSR automatically, to start another program after your program ends, or for creating key macros. You can enter extended key codes (like function keys) by using CHR$(0) before the scan code. PROCEDURE TypeIn (Keys: String); The usual keyboard action is quite sluggish. We can make it a lot crisper by changing the key repeat rate and the delay before repeating. This will work on ATs, but not PC/XT systems. PROCEDURE SpeedKey (RepDelay, RepRate: Integer); The delay may be 0-3 (1 by default): 0 250 milliseconds 1 500 milliseconds 2 750 milliseconds 3 1 second The repeat rate may be 0-31 (11 by default). The larger the number, the slower the speed-- 0 is around 30 cps, and 31 is around 2 cps. I generally prefer to have the keyboard cranked up to full speed, using RepDelay and RepRate both set to zero. This may be a bit too zippy for some people, and may cause keyboard buffer overflows with some games. Experiment with it to see what you like best. Of course, there may be reasons to make keyboard repeat less sensitive instead! That might be a good idea in programs written for small children, for example. You can adjust the keyboard equally well in either direction. Keyboard Control page 17 Unit: Keyboard Pascal allows you to control one of the keys which can interrupt your program, namely the Break key. There's another dangerous key which PasWiz allows you to control-- the PrtSc (PrintScreen) key. PrtSc may not seem like a hazard at first glance, but if it's pressed by accident with no printer ready, or in a graphics mode which PrtSc doesn't understand how to print, the results can be pretty messy. So, we let you turn it off or on: PROCEDURE SetPrtSc (PrtScON: Boolean); Use FALSE to turn it off or TRUE to turn it back on. If you turn off PrtSc, you MUST remember to turn it back on again before your program ends! Otherwise, an interrupt vector will point into nowhere, causing probable chaos the next time PrtSc is pressed. Regardless of whether you've turned the PrtSc key off, you can print the screen yourself just as if PrtSc had been pressed: PROCEDURE PrintScreen; Now here's a strange one for you. When IBM brought out the 101-key keyboard, called the "enhanced" keyboard, they did something bizarre to the BIOS. They still allowed old keyboard calls to work, but they filtered out the new key codes so no one would see them. This made sure that no one would be able to use the capabilities of the "enhanced" keyboard without rewriting their programs. So, the keyboard has been around for years, and there are still few programs that even notice when you press F11. Pascal does not support the enhanced keyboard at all. Fortunately, PasWiz -does-. You can find out if an enhanced keyboard is installed with the EnhKbd function, which is in the Equipment unit. If there is an enhanced keyboard installed, you can activate it with this: PROCEDURE SetEnhKbd (Enhanced: Boolean); With enhanced keyboard support activated, all key requests that used the old services are translated to the new services. So, SetEnhKbd affects the ReadKey (in the CRT unit that comes with Pascal) and other Pascal functions as well as other PasWiz keyboard routines. Note that you MUST deactivate enhanced keyboard support before ending your program. Otherwise, an interrupt vector will point into nowhere, probably causing a crash on the next keypress! Keyboard Control page 18 Unit: Keyboard If you're about to request important input, you may not want to chance having it answered from results of the keyboard buffer-- could be that the user meant those keys for another purpose. In that case, it's a good approach to clear out the keyboard buffer just before the input: PROCEDURE ClearKbd; No keyboard unit would be complete without a selection of routines to check the shift states and get or set the keyboard toggles. Let's start with the toggles, which are so called because they get toggled from one state to another: FUNCTION CapsOn: Boolean; { Caps Lock } FUNCTION InsertOn: Boolean; { Insert } FUNCTION NumOn: Boolean; { Num Lock } FUNCTION ScrollOn: Boolean; { Scroll Lock } You can also turn the toggles off or on. It's courteous to restore the original toggle states once you end your program, so you might want to save the original values for that purpose. Then again, I guess that doesn't apply if your program is designed for the specific purpose of setting the toggles! PROCEDURE SetCaps (CapsLock: Boolean); { Caps Lock } PROCEDURE SetInsert (InsertKey: Boolean); { Insert } PROCEDURE SetNum (NumLock: Boolean); { Num Lock } PROCEDURE SetScroll (ScrollLock: Boolean); { Scroll Lock } Does anyone actually use ScrollLock for anything? Just curious... Keyboard Control page 19 Unit: Keyboard The shift keys are unique in many respects. They don't return codes that can be detected with ReadKey or stuffed into the keyboard buffer; several can be pressed at the same time; and they don't repeat. You can detect 'em with PasWiz, at any rate: FUNCTION AltPress: Boolean; { any ALT } FUNCTION CtrlPress: Boolean; { any CTRL } FUNCTION ShiftPress: Boolean; { any SHIFT } FUNCTION LAltPress: Boolean; { left ALT } FUNCTION LCtrlPress: Boolean; { left CTRL } FUNCTION LShiftPress: Boolean; { left SHIFT } FUNCTION RAltPress: Boolean; { right ALT } FUNCTION RCtrlPress: Boolean; { right CTRL } FUNCTION RShiftPress: Boolean; { right SHIFT } NOTE that LAltPress, LCtrlPress, RAltPress, and RCtrlPress are ONLY available for enhanced keyboards. They will not return useful results on older keyboards. String Stuff page 20 Unit: Strings Strings have always been something of an afterthought in Pascal. This unit brings in the heavy artillery! The PasWiz string routines may be divided into the following categories: comparison, compression, encryption, extraction, searching, and miscellaneous (mostly alteration). Let's take up these categories one at a time. The compression routines are designed to work with ordinary text strings. The strings may not contain IBM extended-ASCII codes (CHR($80)-CHR($FF)), as these are used in the compression. Compression works on spaces, which will give you an average of 15% compression on normal text. If you compress printable text (i.e., no control codes), you'll get printable text in return, which makes these routines handy for use with text files. Compression and decompression is done at an extreme rate of speed and will not affect the timing of your program in any noticeable fashion. FUNCTION Bsq (St: String): String; { compress a string } FUNCTION BUsq (St: String): String; { uncompress a string } Comparing two strings is easy enough in Pascal, but not as flexible as might be desired for some applications. You can tell if two strings match exactly, but not if they're reasonably close matches. PasWiz has two fuzzy comparison routines which can help. The Soundex routine uses a long-established algorithm to convert a word into a code which represents the sound of the word. It is fast and can work well under properly defined circumstances; for instance, it will tell you that "Smith" sounds just like "Smythe", but not like "Banks". Between the need for speed and the vagaries of the English language, however, you may find matches which don't work as well. Soundex is certain that "Knight" sounds just like "Smashed", for example. So, it may be perfect for something like a phone book, but not for a spelling checker! FUNCTION Soundex (St: String): String; The Bickel routine, on the other hand, tells you how closely two words match. This is a relative measure, not an absolute measure, so you'd probably want to search through an entire dictionary and just keep the words which matched best. The Bickel algorithm is slower than Soundex but is much more precise. FUNCTION Bickel (St1, St2: String): Integer; String Stuff page 21 Unit: Strings There are many cases in which you might want to keep data private. The PasWiz encryption routines provide a simple and extremely fast method for password-protecting text. The encryption technique is trivial and will not withstand any concerted attack, but should suffice for casual use. It will help if you pick a longish password of unusual characters, for example '[^.mE@@&o}' (don't use this example itself, please)! There are two encryption routines (which also work as decryption routines). The first is designed to produce printable, if bizarre-looking, results which are suited for use with text files. Text to be encrypted by this routine may not include IBM extended-ASCII characters (CHR($80) - CHR($FF)), since these are used to insure printable text. FUNCTION CipherP (St, Passwd: String): String; The other routine will work with any sort of text, but is not guaranteed to produce printable results. So, it's not suited for use with text files. FUNCTION Cipher (St, Passwd: String): String; Of the extraction routines, two are merely convenient rephrasings of the Copy function which allow you to grab a substring from the left or right side of a string: FUNCTION Left (St: String; Len: Integer): String; FUNCTION Right (St: String; Len: Integer): String; Another extraction routine is more interesting. It allows you to extract a substring from a string which contains delimited information. For instance, consider the following name and address string. It has three fields, each separated by an asterisk. We can use Extract to grab any individual field: St := 'Charon Software*260 So. Woodruff #304*Idaho Falls, ID 83401'; FOR PrtAddress := 1 TO 3 DO BEGIN AddressLine := Extract(St, '*', PrtAddress); WriteLn(AddressLine); END; The extract function is defined as follows: FUNCTION Extract (St, Delimiter: String; Index: Integer): String; If you try to extract a field which doesn't exist, a null string will be returned. Field delimiters may be anything at all, by the way-- one possible use for Extract might be to block-read a text file, splitting it apart in memory by using the carriage return and linefeed as a delimiter. String Stuff page 22 Unit: Strings PasWiz provides an assortment of string search routines. One is a simple modification of Pos which allows you to start the search at a given place in the string: FUNCTION Instr (Start: Integer; SubSt, St: String): Integer; Another works like Pos, but returns the last match rather than the first match: FUNCTION RPos (SubSt, St: String): Integer; Of course, you don't always want to search for something in specific. You might be more interested in finding the first character that fits into a given category or categories, like perhaps numbers or letters. No problem: FUNCTION TypePos (ChType: Integer; St: String): Integer; The ChType value is formed by adding the numbers which represent the desired categories. This can also be used to search for the first character which is not of a given type, since the categories are exclusive: 1 alphabetic 2 numeric 4 symbols 8 control codes (ASCII 0-31, 127) 16 graphics codes (ASCII 128-255) 32 space (ASCII 32) That covers the string routines which can be readily categorized. Most of the remaining routines are designed to alter a string in one way or another. This includes being able to remove selected characters, substrings, types of characters, or repeated characters from a string; trimming the left or right side of a string of blanks; converting to uppercase, lowercase, or using the correct capitalization for a proper name; replacing one substring with another; reversing a string; and forming a new string by repeating a given substring. The "case", "trim", and "reverse" functions are pretty much self-explanatory: FUNCTION LowerCase (St: String): String; { lowercase } FUNCTION NameCase (St: String): String; { name case } FUNCTION UpperCase (St: String): String; { upper case } FUNCTION LTrim (St: String): String; { left trim } FUNCTION RTrim (St: String): String; { right trim } FUNCTION Reverse (St: String): String; { reverse } String Stuff page 23 Unit: Strings The Crunch function removes adjacent repetitions of a substring. This is particularly handy for parsing user input, for example-- you can use it to remove repeated spaces between options, etc. FUNCTION Crunch (SubSt, St: String): String; The Dupe function forms a string by repeating a substring. It's handy when you need a string of a given number of spaces, zeroes, or nulls. It can also be used for drawing horizontal lines and other applications. FUNCTION Dupe (Count: Integer; SubSt: String): String; There are many times when it's nice to be able to replace all occurrences of one substring with another. PasWiz can do that, no problem. You don't have to worry about recursion, either-- each occurrence is replaced only once, even if the replacement substring contains the target substring. FUNCTION Replace (OldSubSt, NewSubSt, St: String): String; Of course, you can delete a substring by replacing it with a null string. It's faster to use the routine designed for that purpose, though: FUNCTION StripSt (SubSt, St: String): String; You may also delete specified characters from a string: FUNCTION StripCh (ChList, St: String): String; Next, a routine which deletes specified types of characters from a string. This can be very good for screening user input. FUNCTION StripType (ChType: Integer; St: String): String; The ChType value is formed by adding the numbers which represent the desired categories: 1 alphabetic 2 numeric 4 symbols 8 control codes (ASCII 0-31, 127) 16 graphics codes (ASCII 128-255) 32 space (ASCII 32) PasWiz also has a specialized variant on the Val function which converts a number from a string to a WORD (or INTEGER) value. No error checking is provided. This is very small and fast: FUNCTION WVal (St: STRING): WORD; Music page 24 Unit: Music Granted, the PC has never been known for its wonderful sound capabilities. Still, it has more potential than you might guess, especially given the utterly minimal Sound/Delay/NoSound routines that are provided with Pascal. The Music unit makes it much easier to use sounds by providing an actual music language. The BACHINV and ENTERTNR demo programs demonstrate some of the possibilities. The PasWiz music language is nearly identical to that offered by the BASIC PLAY statement. The major difference is that the "MB" command (play the music as a background task) is not supported. I'll add that later, if I get enough requests. There are only two music procedures: PROCEDURE ResetMF; PROCEDURE PlayMF(Sounds: String); The ResetMF procedure is used to reset all music parameters to the default values. It would typically be used after finishing a song to restore the music handler for the next song. The PlayMF procedure is the one that does the real work. Here's a list of the music commands supported: MB play music as a background task (ignored) MF play music as a foreground task (ignored) ML legato (8/8 note length) MN normal music (7/8 note length) MN staccato (6/8 note length) Ln Length of notes (n = 1-64; note length = 1/n) Nn Note number (n = 0-84; 0 is a rest) On Octave (n = 0-6, default 4) Pn Pause (n = 1-64; pause length = 1/n) Tn Tempo (n = 32-255, default 120; quarter notes/minute) < move up an octave (max 6) > move down an octave (min 0) You can also use the actual letters of the notes (C, D, E, F, G, A, and B). If you're not particularly musical, these correspond to "do, re, mi, fa, so, la, ti" (with the final "do" being C again, but an octave higher). To play a scale, you'd use 'CDEFGAB>C'. The notes may be followed by dots, by note lengths, and by sharp or flat symbols (a '+' or '#' for a sharp, a '-' for a flat). For example, 'D-.' is a dotted D flat. The dot means that the note will play for half again its usual length. Dots can be repeated. I might note (ahem) that the so-called "ANSI" music offered by some BBSes and comm programs is based on this same music language. If you're writing telecomm software, this unit makes it trivial to add sound. Mouse page 25 Unit: Mouse Considering the ubiquity of the mouse these days, it's a marvel to me that none of the popular programming languages provides any support for it. Well, BASIC supports it via light pen emulation, but that scarcely counts. Anyway, this is a simple little unit which provides you direct access to the mouse driver. It will work with any Microsoft-compatible mouse driver. A few words are necessary on mouse handling. To begin with, the best time to check for a mouse is immediately after you've established the desired video mode. The mouse driver initializes certain information when you check for it, so it's a good idea to check for it only once, at the appropriate time. The mouse cursor was implemented in a rather bizarre manner. If you call ShowCursor, you're guaranteed that the mouse cursor will be visible; however, this also increments an internal visibility flag. If you call ShowCursor multiple times, you will also have to call HideCursor multiple times before the dang cursor actually disappears. The mouse driver was apparently implemented without any thought for the future. In text mode, it returns coordinates based on CGA hi-res graphics mode-- 640x200 instead of the expected 80x25. If you use the mouse driver in text mode, you'll have to compensate for this (divide coordinates by 8 to convert to text mode, or multiply by 8 to convert to the mouse virtual mode). The CGA low-res 320x200 graphics mode is implemented likewise. It seems Microsoft thought that 640x200 would be the ultimate resolution, so they translated everything to a 640x200 virtual mode. Arrgh. Fortunately, this does not apply to video modes other than text and CGA graphics. I understand that it's possible to get the mouse working in Hercules graphics mode, but I'm not sure what the details are. If anyone out there knows, please let me know, and I'll pass it along. Since Microsoft was initially very tight-lipped about the mouse functions, older mouse drivers for Microsoft-compatible rodents don't necessarily have all the functions available (this includes Logitech, amazingly enough). The Microsoft mouse driver continues to be updated in a rapid and haphazard manner, so keeping up-to-date is no guarantee. However, if you have any problems with these routines, you can almost certainly clear them up by getting a newer mouse driver. The routines in this unit are almost all old, well-established functions. "Iffy" ones are marked for your convenience. Mouse page 26 Unit: Mouse The first function to consider simply tells you whether a mouse is available, and if so, how many buttons it has. It also resets the mouse driver, so it should be used only at the beginning of your program (or after changing the video mode). FUNCTION Init: Integer; { returns 0 if no mouse, else # of buttons } Most often, you'll want to know which mouse button(s) are pressed and where the mouse cursor is: FUNCTION LeftButton: Boolean; { TRUE if pressed } FUNCTION MidButton: Boolean; { always FALSE on 2-button rodents } FUNCTION RightButton: Boolean; { TRUE if pressed } FUNCTION WhereX: Integer; { X-coord (see notes on previous page) } FUNCTION WhereY: Integer; { Y-coord (see notes on previous page) } As well as finding the current mouse information, it's possible to find out what the mouse has been doing since you last checked. Two sets of routines exist: one to find out how many times a button was pressed and where it was at the last press, and one to find out how many times a button was released and where it was at the last release. PROCEDURE LeftClick(VAR Count, X, Y: Integer); PROCEDURE MidClick(VAR Count, X, Y: Integer); PROCEDURE RightClick(VAR Count, X, Y: Integer); PROCEDURE LeftRelease(VAR Count, X, Y: Integer); PROCEDURE MidRelease(VAR Count, X, Y: Integer); PROCEDURE RightRelease(VAR Count, X, Y: Integer); Mouse page 27 Unit: Mouse There's one more informational routine. It tells you the basic mouse software and hardware stats: driver version, connector type (serial, bus, etc), and IRQ used. This routine is slightly hazardous in that, although Microsoft claims it has existed from the first, other manufacturers (including Logitech) have taken some time to implement it. In other words, it may not return useful information. The routine conducts a self-check and will return all zeroes if it suspects the information is not good, but even so... proceed with care. PROCEDURE Info(VAR Version: Real; VAR Connector, IRQ: Byte); Connector info: 1=bus, 2=serial, 3=InPort, 4=PS/2, 5=Hewlett-Packard IRQs: 0=PS/2; 2-5, 7 are actual IRQ numbers. It is conceivable that higher IRQ numbers may be supported on AT-type machines, though not by Microsoft. Of course, you can set mouse info as well as retrieving it. The following routines are pretty well self-explanatory (don't forget the notes about the strange text-mode and CGA coordinate handling, and cursor handling): { hide the mouse cursor } PROCEDURE HideCursor; { (maybe) make the cursor visible } PROCEDURE ShowCursor; { set the cursor position } PROCEDURE GotoXY(X, Y: Integer); { set the cursor range } PROCEDURE Window(X1, Y1, X2, Y2: Integer); There are other possibilities supported by the mouse driver that I have not implemented here. They're mostly on the rather esoteric side and are not supported by anything except recent Microsoft mouse drivers and perhaps some close compatibles, such as Logitech. Among the mouse capabilities I haven't implemented are finding out how fast and in what direction the mouse is moving, the coordinate size of the screen as far as the mouse is concerned, finding out the video modes that the mouse driver understands, setting mouse speed and sensitivity parameters, setting up an interrupt-driven system for handling mouse events, and a few others. Mouse page 28 Unit: Mouse None of these missing capabilities is particularly vital (or even useful) for the average mouse application. However, if you're interested in any of these capabilities, let me know which ones, and I'll be glad to add them to PasWiz. Note: if you're confused at how I can get away with duplicating the names of existing procedures... well, it's easy enough. Let's consider WhereX. It means one thing to Pascal's Crt unit and another to the PasWiz Mouse unit. Since the Mouse unit doesn't use the Crt unit, it has no problem with any ambiguity. Existing programs that don't use the Mouse unit will likewise have no problems, since they don't know about the new routine. Suppose you want to use both the Crt and Mouse units together, however? There's the rub! You can specify a routine from a given unit by including the unit name when you use the routine. For instance: X := Mouse.WhereX; { get the mouse cursor position } X := Crt.WhereX; { get the normal cursor position } This allows multiple units to use the same names for things. While this may seem like a bit of a pain, since you have to specify which you want, it has a major built-in advantage if used properly. What's properly? When a routine in one unit works just like a routine by the same name in another unit. When I call the mouse routine "WhereX", for instance, I've also told you what it is for and how to use it. So, the name itself contains a lot of useful information on how to use the routine! That's what I call a major advantage.