CCUCARD%indsvax1.bitnet@uicvm.uic.edu (Paul Cardwell) (11/24/89)
I was trying to figure out a way to read the keyboard on the Amiga. I figured out how to do it under a CLI window. I was wanting a better one, but here it is: #define ESC 27 main() { int status, win_ptr; char key, buffer[256]; win_ptr = open("RAW:50/50/200/60/Read Keyboard", 0, 0); if(win_ptr != -1) { do { status = read(win_ptr, &key, 1); /* Read from win_ptr and get a char */ sprintf(buffer, "You pressed |%c| Code |%d|\n", key, key); write(win_ptr, buffer, strlen(buffer)); /* Write to Window */ } while (key !=ESC); /* While key is not an ESCape character do... */ close(win_ptr); /* Okay, close window pointed by win_ptr */ } } I was wanting to know how to do it using CUSTOM SCREENS with of course CUSTOM WINDOWS. like : struct NewWindow MyWindow = { 0,0, /* Location of window */ 320,200,3 /* Width..Height..Depth */ ... And the rest of the setup. }; I also create the SCREEN for it too, anyway: if (( Wdw = (struct Window *)OpenWindow(&MyWindow))==NULL) exit(0); /* Failed to open window */ So do I read like: read(MyWindow, &key, 1); ??? I doubt thats correct ??? _____________________________________________________ / //// / / / _ _ //// Paul Cardwell / Amiga / / \\\\//// ccucard@indsvax1 / Rules!!! / / \\//// Indiana State University / / /______\/\/______________________________/___________/
fgd3@jc3b21.UUCP (Fabbian G. Dufoe) (11/26/89)
In article <4734@nigel.udel.EDU>, CCUCARD%indsvax1.bitnet@uicvm.uic.edu (Paul Cardwell) asks about reading the keyboard while using Intuition windows. Several people have asked about this topic recently and I just finished writing some routines to do just that, so here's the code. It's a bit long for this newsgroup. I hope that doesn't inconvenience anyone. If anyone knows a shorter way to read the keyboard without sacrificing full keymap support I'd sure be interested in learning about it. --Fabbian Dufoe 350 Ling-A-Mor Terrace South St. Petersburg, Florida 33705 813-823-2350 UUCP: ...uunet!pdn!jc3b21!fgd3 ----------------------------------------------------------------------- # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file" # Created Sat Nov 25 21:43:56 1989 # # This archive contains: # Keyboard.doc # Keyboard.h # Keyboard.c # RawDemo.c echo "Creating Keyboard.doc" cat > Keyboard.doc <<"***EOF Keyboard.doc***" KEYBOARD.C--Translating Inutition RAWKEY Class Messages into Useful Codes by Fabbian G. Dufoe, III INTRODUCTION Getting ordinary keystrokes into your program is a tricky business on the Amiga if you want to use Intuition's windows and other features. Intuition provides two IDCMP flags: VANILLAKEY and RAWKEY. VANILLAKEY returns ASCII codes for the standard ASCII characters but doesn't tell you anything about functions keys, cursor keys, or the help key. RAWKEY identifies the physical key which was pressed buy you have to do some extra processing to relate it to the key value assigned to it in the user's keymap. Keyboard.c does that extra processing for you. This document explains how to call the three functions to open the console device, close the console device, and get ASCII keycodes or unique key identifiers from the console device. This document and the other files in this archive are in the public domain. You may use them any way you wish. FILES IN THIS ARCHIVE This archive contains the following files: Keyboard.c Source code for functions to translate RAWKEY Intuition messages to usable keycodes Keyboard.doc Documentation for Keyboard.c Keyboard.h Function prototypes and declarations for programs using Keyboard.c Keyboard.o Object file compiled from Keyboard.c with the Lattice AmigaDOS C compiler, version 5.02. You can use this as a link-time library if you have a compiler that uses the standard Amiga object format. RawDemo Executable demonstration program that uses the Keyboard.c functions RawDemo.c Source code for Keyboard.c demonstration program RawDemo.lmk Input for the Lattice lmk utility for compiling RawDemo.c and Keyboard.c QUICK INSTRUCTIONS To use the functions in Keyboard.c you must include Keyboard.h in your source file (#include "Keyboard.h"). If your C compiler does not support function prototypes define the symbolic constant "__NOPROTO" in your source file (#define __NOPROTO). Open the console device by calling OpenReadConsole(). OpenReadConsole() takes no arguments. It returns 0 if it succeeds or -1 if it fails. When you get an Intuition RAWKEY class message call ReadKey() to decode it. You have to set the IDCMP RAWKEY flag when you open your window to get RAWKEY class messages. ReadKey() requires three arguments. 1. A pointer to the Intuition message (struct IntuiMessage *) 2. A pointer to the key ID number (unsigned short int *) 3. A pointer to the keymap (struct KeyMap *) To use the default keymap set the pointer to NULL. ReadKey() returns a char. If the key can be represented by a standard ASCII character ReadKey() will return that character. Note that control characters are included in this category. ReadKey() returns 0 for keys which can't be represented by an ASCII character. In that case the key can be identified by the value in the KeyID field. The value will match one of the symbolic constants defined in Keyboard.h. If ReadKey() fails it returns -1. If the message was not a RAWKEY class message or if it was a "key up" message ReadKey() returns -2. You can safely ignore return values of -2. Close the console device by calling CloseReadConsole(). CloseReadConsole() takes no arguments and returns nothing. THEORY The console device (like all Amiga devices) is also a library. It provides a function called RawKeyConvert() which translates Intuition RAWKEY messages into the sequences you would get if you were reading the console device directly. RawKeyConvert would generate 0x9b 0x30 0x7e if you pressed the F1 key. To use RawKeyConvert() you need a pointer to the console device just as you need a pointer to the Intuition library to use any Intuition functions. OpenReadConsole() calls the exec function OpenDevice with a unit number of -1. That gets a pointer to the device library vector without opening an actual console. You need to get the pointer before you call ReadKey() because ReadKey() uses RawKeyConvert(). If you want to write to the console device you'll have to call OpenDevice() directly. That's beyond the scope of this document. You'll need to look in the ROM Kernel Manual: Libraries and Devices for discussion of the console device (chapter 8), the ROM Kernel Manual: Exec for discussion of messages and ports (chapter 3) and input and output (chapter 4), and the Autodocs for information about OpenDevice (Autodocs1.3/DevicesA-K/console.doc). ReadKey() passes the Intuition message along to DeadKeyConvert() which calls RawKeyConvert(). DeadKeyConvert is taken substantially from the 1.2 Enhancer manual (page 65). Dead keys allow you to add diacritical marks to some characters. On the usa keymap there are five dead keys: F, G, H, J, and K. If you hold down the ALT key and press F nothing happens. That's why it's called a dead key. Now press e. There will be an accent mark above the e (i). RawKeyConvert() doesn't know about dead keys, so you need to use DeadKeyConvert() as a front end. Both RawKeyConvert() and DeadKeyConver() place a sequence of codes in a buffer. The sequence can vary in length. ASCII characters require a single code. Function keys may require four. ReadKey() parses the sequences returned by DeadKeyConvert() and reduces everything to a single code. Printable characters (and control characters) are returned to the caller. It identifies special keys by placing a code in the KeyID field pointed to by the caller. The caller does not have to examine the KeyID field unless ReadKey() returns 0. DOCUMENTATION OF FUNCTIONS The interface parameters for each function in Keyboard.c are included as comments in the source code. ***EOF Keyboard.doc*** echo "Creating Keyboard.h" cat > Keyboard.h <<"***EOF Keyboard.h***" /* Keyboard.h by Fabbian G. Dufoe, III This is a public domain program. You may use it any way you like. This file contains function prototypes and definitions for using the functions in Keyboard.c. If your compiler does not support function prototypes you must define the preprocessor symbol "__NOPROTO" to compile programs using this file. */ #include <dos.h> #ifndef INTUITION_INTUITION_H #include <intuition/intuition.h> #endif #ifndef KEYBOARD_H #define KEYBOARD_H TRUE #define K_UP 301 /* Arrow keys */ #define K_DOWN 302 #define K_RIGHT 303 #define K_LEFT 304 #define K_S_UP 305 /* Shifted arrow keys */ #define K_S_DOWN 306 #define K_S_RIGHT 307 #define K_S_LEFT 308 #define K_HELP 309 /* Help key */ #define K_F1 331 /* Function keys F1-F10 */ #define K_F2 332 #define K_F3 333 #define K_F4 334 #define K_F5 335 #define K_F6 336 #define K_F7 337 #define K_F8 338 #define K_F9 339 #define K_F10 340 #define K_S_F1 341 /* Shifted function keys F1-F10 */ #define K_S_F2 342 #define K_S_F3 343 #define K_S_F4 344 #define K_S_F5 345 #define K_S_F6 346 #define K_S_F7 347 #define K_S_F8 348 #define K_S_F9 349 #define K_S_F10 350 /* Prototypes for functions defined in Keyboard.c */ #ifndef __NOPROTO #ifndef __PROTO #define __PROTO(a) a #endif #else #ifndef __PROTO #define __PROTO(a) () #endif #endif void CloseReadConsole __PROTO((void)); int DeadKeyConvert __PROTO((struct IntuiMessage *Message, unsigned char *KeyBuffer, int BufferSize, struct KeyMap *KeyMap)); int OpenReadConsole __PROTO((void)); char ReadKey __PROTO((struct IntuiMessage *Message, unsigned short *KeyID, struct KeyMap *KeyMap)); #endif ***EOF Keyboard.h*** echo "Creating Keyboard.c" cat > Keyboard.c <<"***EOF Keyboard.c***" /* Keyboard.c by Fabbian G. Dufoe, III This is a public domain file. You may use it any way you wish. The functions in this file take an Intuition RAWKEY class message and return the value of the key which was pressed. For standard ASCII characters ReadKey() returns the character. For function keys it returns a zero and places a code representing the key in a buffer supplied by the calling program. The codes are defined in Keys.h. */ #include "Keyboard.h" struct Device *ConsoleDevice = NULL; static struct IOStdReq ConsoleMsg; void CloseReadConsole() /* FUNCTION This function closes the Console Device opened by OpenReadConsole(). If the Console Device was not successfully opened CloseReadConsole() will return without doing anything. INPUT None RESULTS None */ { if (ConsoleDevice != NULL) CloseDevice(&ConsoleMsg); } #ifdef LATTICE int DeadKeyConvert(struct IntuiMessage *Message, UBYTE *KeyBuffer, int BufferSize, struct KeyMap *KeyMap) #else int DeadKeyConvert(Message, KeyBuffer, BufferSize, KeyMap) struct IntuiMessage *Message; UBYTE *KeyBuffer; int BufferSize; struct KeyMap *KeyMap; #endif /* FUNCTION This function converts an Intuition RAWKEY message to the kind of keycodes returned by the Console Device. It uses the Console Device's RawKeyConvert() function. INPUT Message - a pointer to the intuition message KeyBuffer - a pointer to the buffer the user supplied for keycodes BufferSize - the size of KeyBuffer KeyMap - a pointer to a KeyMap structure to be used for the conversion. A NULL value selects the default KeyMap. RESULTS The function returns -2 if the message was not a RAWKEY class message. If the number of keycodes produced was greater than BufferSize the function returns -1. Otherwise the function returns the number of keycodes it placed in the buffer. */ { static struct InputEvent InputEvent = { NULL, /* struct InputEvent *ie_NextEvent; */ IECLASS_RAWKEY, /* UBYTE ie_Class; */ 0, /* UBYTE ie_SubClass; */ 0, /* UWORD ie_Code; */ 0 /* UWORD ie_Qualifier; */ /* union */ /* { */ /* struct */ /* { */ /* WORD ie_x; */ /* WORD ie_y; */ /* } ie_xy; */ /* APTR ie_addr; */ /* } ie_position; */ /* struct timeval ie_TimeStamp; */ }; if (Message->Class != RAWKEY) return(-2); /* if (Message->Code & IECODE_UP_PREFIX) return(0); */ InputEvent.ie_Code = Message->Code; InputEvent.ie_Qualifier = Message->Qualifier; InputEvent.ie_position.ie_addr = (APTR)*(Message->IAddress); return(RawKeyConvert(&InputEvent, KeyBuffer, BufferSize, KeyMap)); } int OpenReadConsole() /* FUNCTION This function gets a pointer to the Console Device by opening a Console Device without attaching it to any window. Its purpose is to make the Console Device function RawKeyConvert() available. INPUT None RESULTS The function returns 0 if it succeeds, -1 if it failed to open the Console Device. */ { if (OpenDevice("console.device", -1L, &ConsoleMsg, 0) != 0) return(-1); ConsoleDevice = (struct Device *)ConsoleMsg.io_Device; return(0); } #ifdef LATTICE char ReadKey(struct IntuiMessage *Message, unsigned short int *KeyID, struct KeyMap *KeyMap) #else char ReadKey(Message, KeyID, KeyMap) struct IntuiMessage *Message; unsigned short int *KeyID; struct KeyMap *KeyMap; #endif /* FUNCTION This function converts an Intuition RAWKEY message to an ASCII character or an integer code identifying the special key pressed. INPUT Message - a pointer to the Intuition message KeyID - a pointer used to return the ID code of a function key KeyMap - a pointer to the keymap structure to be used for the conversion. A NULL pointer specifies the default keymap. RETURNS If the function converts a RAWKEY message to an ASCII character it returns that character. It returns zero if a special key was pressed and it places the key's ID code in the integer pointed to by KeyID. If the message was not a RAWKEY class message or if it was a "key up" message ReadKey() returns -2. The calling program can ignore any calls which return -2. If it fails it returns -1. */ { int actual; UBYTE KeyBuffer[10]; *KeyID = 0; if (Message->Class != RAWKEY) return(-2); /* If it's not a RAWKEY message we'll just ignore it. We tell the caller it can ignore it, too. */ if (Message->Code & IECODE_UP_PREFIX) return(-2); /* If it's a key up message we'll ignore it and tell the caller to ignore it, too. */ actual = DeadKeyConvert(Message, KeyBuffer, sizeof(KeyBuffer), KeyMap); if (actual == 1) return((char)KeyBuffer[0]); /* If DeadKeyConvert() converted the message to a single code we can return it to the caller. */ switch (KeyBuffer[0]) { case 0x9b: switch (KeyBuffer[1]) { case ' ': switch (KeyBuffer[2]) { case '@': *KeyID = K_S_RIGHT; break; case 'A': *KeyID = K_S_LEFT; break; default: break; } break; case '?': switch (KeyBuffer[2]) { case '~': *KeyID = K_HELP; break; default: break; } break; case '0': switch (KeyBuffer[2]) { case '~': *KeyID = K_F1; break; default: break; } break; case '1': switch (KeyBuffer[2]) { case '~': *KeyID = K_F2; break; case '0': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F1; break; default: break; } break; case '1': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F2; break; default: break; } break; case '2': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F3; break; default: break; } break; case '3': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F4; break; default: break; } break; case '4': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F5; break; default: break; } break; case '5': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F6; break; default: break; } break; case '6': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F7; break; default: break; } break; case '7': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F8; break; default: break; } break; case '8': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F9; break; default: break; } break; case '9': switch (KeyBuffer[3]) { case '~': *KeyID = K_S_F10; break; default: break; } break; default: break; } break; case '2': switch (KeyBuffer[2]) { case '~': *KeyID = K_F3; break; default: break; } break; case '3': switch (KeyBuffer[2]) { case '~': *KeyID = K_F4; break; default: break; } break; case '4': switch (KeyBuffer[2]) { case '~': *KeyID = K_F5; break; default: break; } break; case '5': switch (KeyBuffer[2]) { case '~': *KeyID = K_F6; break; default: break; } break; case '6': switch (KeyBuffer[2]) { case '~': *KeyID = K_F7; break; default: break; } break; case '7': switch (KeyBuffer[2]) { case '~': *KeyID = K_F8; break; default: break; } break; case '8': switch (KeyBuffer[2]) { case '~': *KeyID = K_F9; break; default: break; } break; case '9': switch (KeyBuffer[2]) { case '~': *KeyID = K_F10; break; default: break; } break; case 'A': *KeyID = K_UP; break; case 'B': *KeyID = K_DOWN; break; case 'C': *KeyID = K_RIGHT; break; case 'D': *KeyID = K_LEFT; break; case 'S': *KeyID = K_S_DOWN; break; case 'T': *KeyID = K_S_UP; break; default: break; } break; default: break; } if (*KeyID == 0) return(-1); else return(0); } ***EOF Keyboard.c*** echo "Creating RawDemo.c" cat > RawDemo.c <<"***EOF RawDemo.c***" /* RawDemo.c by Fabbian G. Dufoe, III This is a public domain program. You may use it any way you want. This program demonstrates the use of the OpenReadConsole(), ReadKey(), and CloseReadConsole() functions in Keyboard.c */ #include "keyboard.h" #define VERSION 34L struct IntuiText IText1 = { 1, 0, JAM1, 4, 12, NULL, "Type some keys.", NULL }; struct NewWindow NewWindow = { 0, 10, /* SHORT LeftEdge, TopEdge, */ 550, 190, /* SHORT Width, Height, */ 0, 1, /* UBYTE DetailPen, BlockPen, */ CLOSEWINDOW | RAWKEY | MOUSEBUTTONS, /* ULONG IDCMPFlags, */ WINDOWDRAG | WINDOWDEPTH | WINDOWCLOSE | WINDOWSIZING | ACTIVATE | SMART_REFRESH, /* ULONG Flags, */ NULL, /* struct Gadget *FirstGadget, */ NULL, /* struct Image *CheckMark, */ "RawDemo", /* UBYTE *Title, */ NULL, /* struct Screen *Screen, */ NULL, /* struct BitMap *BitMap, */ 10, 10, /* SHORT MinWidth, MinHeight, */ 640, 400, /* USHORT MaxWidth, MaxHeight, */ WBENCHSCREEN /* USHORT Type, */ }; void main() { int error = 30; struct IntuiMessage *IMessage; ULONG IMessageClass; extern struct IntuitionBase *IntuitionBase; void PrintKey(struct IntuiMessage *); struct Window *Window; IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", VERSION); if (IntuitionBase) { if (OpenReadConsole() == 0) { if (Window = (struct Window *)OpenWindow(&NewWindow)) { PrintIText(Window->RPort, &IText1, 0, 0); do { Wait(1<<Window->UserPort->mp_SigBit); while ((IMessage = (struct IntuiMessage *) GetMsg(Window->UserPort)) != NULL) { switch (IMessage->Class) { case CLOSEWINDOW: break; case RAWKEY: PrintKey(IMessage); break; case MOUSEBUTTONS: printf("Mouse X: %d\tY: %d\n", IMessage->MouseX, IMessage->MouseY); break; } IMessageClass = IMessage->Class; ReplyMsg(IMessage); } } while (IMessageClass != CLOSEWINDOW); CloseWindow(Window); } else printf("OpenWindow() failed.\n"); CloseReadConsole(); } else printf("OpenReadConsole() failed.\n"); CloseLibrary(IntuitionBase); } else printf("OpenLibrary() failed for intuition.library.\n"); exit(error); } void PrintKey(struct IntuiMessage *IMessage) { char Key; unsigned short int KeyID; Key = ReadKey(IMessage, &KeyID, NULL); if (Key == -1 || Key == -2) return; if (Key != 0) printf("Key: %c\n", Key); else { switch (KeyID) { case K_UP: printf("K_UP\n"); break; case K_DOWN: printf("K_DOWN\n"); break; case K_RIGHT: printf("K_RIGHT\n"); break; case K_LEFT: printf("K_LEFT\n"); break; case K_S_UP: printf("K_S_UP\n"); break; case K_S_DOWN: printf("K_S_DOWN\n"); break; case K_S_RIGHT: printf("K_S_RIGHT\n"); break; case K_S_LEFT: printf("K_S_LEFT\n"); break; case K_HELP: printf("K_HELP\n"); break; case K_F1: printf("K_F1\n"); break; case K_F2: printf("K_F2\n"); break; case K_F3: printf("K_F3\n"); break; case K_F4: printf("K_F4\n"); break; case K_F5: printf("K_F5\n"); break; case K_F6: printf("K_F6\n"); break; case K_F7: printf("K_F7\n"); break; case K_F8: printf("K_F8\n"); break; case K_F9: printf("K_F9\n"); break; case K_F10: printf("K_F10\n"); break; case K_S_F1: printf("K_S_F1\n"); break; case K_S_F2: printf("K_S_F2\n"); break; case K_S_F3: printf("K_S_F3\n"); break; case K_S_F4: printf("K_S_F4\n"); break; case K_S_F5: printf("K_S_F5\n"); break; case K_S_F6: printf("K_S_F6\n"); break; case K_S_F7: printf("K_S_F7\n"); break; case K_S_F8: printf("K_S_F8\n"); break; case K_S_F9: printf("K_S_F9\n"); break; case K_S_F10: printf("K_S_F10\n"); break; } } } ***EOF RawDemo.c***
root@ccave.UUCP (Juergen Hermann) (11/28/89)
>finished writing some routines to do just that, so here's the code. It's a >bit long for this newsgroup. I hope that doesn't inconvenience anyone. Well, I always wanted such code, but hadn't the time to write it myself (thanx, BTW). It worked fine, except the fact that I had to change VERSION from 34L to 33L. If it was binary only, that would've been a MAJOR inconvenience (since the program would simply refuse to run). So, PLEASE, all programmers out there, don't rely on 1.3 ROMs being installed if it's not necessary. Nowadays it's not necessary at all (there are no drastic changes to the library interfaces/no new functions), maybe when 1.4 is out and you want to use all those neat features we are waiting for. While we're at 1.4, can I get a confirmation (not 100%, but maybe 70% :-) from anyone of "The crew that never rests" that ACTION_TRUNCATE will work with 1.4. Currently, it impossible to write a fast & efficient reorganisation for a database since you have to copy the whole database file to get unused space back. Hmm, maybe CrossDos supports it already??! -- // Juergen Hermann root@ccave.smurf.ira.uka.de \X/ 75 Karlsruhe 1, FRG Fido: 2:241/2.1212@FidoNet
steveb@cbmvax.UUCP (Steve Beats) (12/01/89)
In article <83@ccave.UUCP> root@ccave.UUCP (Juergen Hermann) writes: >While we're at 1.4, can I get a confirmation (not 100%, but maybe 70% :-) >from anyone of "The crew that never rests" that ACTION_TRUNCATE will work To coin a phrase, "it`s in there". ACTION_SET_FILE_SIZE is the packet interface, while the DOS interface will be something like SetFileSize(file,size) Goodenuff? Steve
fgd3@jc3b21.UUCP (Fabbian G. Dufoe) (12/03/89)
From article <83@ccave.UUCP>, by root@ccave.UUCP (Juergen Hermann): > Well, I always wanted such code, but hadn't the time to write it myself > (thanx, BTW). It worked fine, except the fact that I had to change > VERSION from 34L to 33L. If it was binary only, that would've been a > MAJOR inconvenience (since the program would simply refuse to run). Good point! It's a good thing I distributed the source code. I was just experimenting with version numbers and didn't think about folks not having 1.3. You see, those of us with A1000s can upgrade a lot easier than you folks with ROM-based systems. Glad you found the code helpful. --Fabbian Dufoe 350 Ling-A-Mor Terrace South St. Petersburg, Florida 33705 813-823-2350 UUCP: ...uunet!pdn!jc3b21!fgd3
jshortle@jarthur.Claremont.EDU (John Shortle) (04/08/90)
I have a question about reading the keyboard from a C program and am a bit confused by the numerous programming manuals I have. What I want to do is the following: Change the threshhold and key repeat speeds from within my program. Then I will have a loop that does the following: checks for a keyboard input; if nothing, continue with the rest of the code in the loop. if there is an input, figure out what key was pressed, and take appropriate action (but don't print the character), then continue with the rest of the code in the loop. If the user holds down a key, I want inputs to occurs as often as is specified by the keyboard threshhold and repeat values. It seems that the input.device would be the best way of doing this, but I am not sure how to read the keyboard from the input device. Is there an easy way of doing this (without adding an input handler, or should I be looking into a console or keyboard device)? Thanks for you help. Please reply through E-mail, unless you think others will be helped by your comments. John Shortle (jshortle@jarthur.claremont.edu)