1 Read Me First These articles are reprinted from the March '91 edition of TechNotes/dBASE IV. Due to the limitations of this media, certain graphic elements such as screen shots, illustrations and some tables have been omitted. Where possible, reference to such items has been deleted. As a result, continuity may be compromised. TechNotes is a monthly publication from the Ashton-Tate Software Support Center. For subscription information, call 800-545-9364. 2 Q&A Command Editing On the Fly Behind every dot prompt expression there's an editing window. There's No Place Like Ctrl-Home My dot prompt expressions are starting to be quite lengthy. As the amount of parenthetical insets increases, I find it more and more difficult to scroll back and forth trying to determine where possible errors exist. What can I do to make debugging dot prompt expressions less cumbersome? By pressing Ctrl-Home, you enter the text editor. Here, you can use the Enter key to break complex expressions, thus becoming easier to view (it's all on the screen, either auto-wrapped or explicitly broken by lines) and debug. To return to the dot prompt, simply press Ctrl-End and your command is processed. To cancel, press Escape. You most likely have never gotten to the point where your expressions exceed 254 characters. In that case, you would receive the warning that your command was too long and how to work around the limitation. Memo Exporting in WordPerfect Although I've been able to export databases without memos into WordPerfect, I'm curious as to which command to use to also export memo information into that environment. Is there a way to do this? It is easy enough to export a file without memos into a more universal text format such as: COPY TO DELIMITED and then translate the file into a WordPerfect format using the CONVERT utility that WordPerfect provides. However, when translating a file and its memo information, a small program is required, as shown below. SET TALK OFF USE SET ALTERNATE TO Txtout SET ALTERNATE ON SCAN ? '"' ?? TRIM() ?? '",' ?? LTRIM(STR(, 10, 2)) ?? ',"' ?? DTOS() ?? ',"' ?? IIF(, ".T.", ".F.") ?? ',"' lcnt = 1 DO WHILE lcnt < MEMLINE() + 1 ?? MLINE(note, lcnt) lcnt = lcnt + 1 ENDDO ?? '"' ENDSCAN CLOSE ALTERNATE RETURN The ?? lines where TRIM(), DTOS() and IIF() are used on various field types illustrate how to do such fields. Additionally, the ?? statements which contain only commas, double and single quotes are important to delimit the different fields correctly. The italic markers shown in angle brackets should be replaced with the actual names of the fields of your database. They can appear in whatever order you prefer. In the WP directory run CONVERT. When prompted, enter the input filename (with .TXT extension) and the output filename. Select option 9 from the next menu. Enter a comma as a field delimiter, and as record delimiter, type {13}{11}. The character to strip out would be a double quote. After entering these parameters, process the conversion. The Letter of the Law (of Indexing) I am attempting to index on a field that contains part numbers. The part numbers sometimes begin with letters, sometimes with numbers. What I want is an index that shows the part numbers beginning with letters first (ascending alphabetically). Afterwards, the part numbers starting with numbers would be listed sequentially. None of the ASCII or dictionary sorts can accomplish this. Is there a way? Your request is possible by way of a complex index expression. Suppose your part number field is 10 characters in width and is called PART. Your index expression would appear as follows: INDEX ON STR(VAL(Part), 10) + SUBSTR(Part, 1, 10) TAG One exception to this index expression would prevent part numbers with leading zeros from being considered as a numeric (the VAL() function would remove the leading zeros in its string to numeric conversion). So a part number such as 00AJ98207 would be evaluated starting from its third position, or the value "A" since the leading zeros would be stripped by the STR() and VAL() conversion. If a part number started with a letter, it would be interpreted as a zero after being filtered by the VAL() function. You might try displaying the above index expression from the dot prompt to see the effect of the expression on your field. ? STR(VAL(Part), 10) + SUBSTR(Part, 1, 10) This index expression would not be most ideal for a SEEK operation and is more suited toward a logical sort for reporting purposes. However, seeing how the index expression is laid out will give you a good idea as to how you might SEEK with such an index. Save the Last Edit For Me I have developed a format screen in which the last field must have an editing condition satisfied. The validation works fine except for those times where I realize there is data in above fields that requires further editing. In that case, I am stuck in the last field requiring the valid entry and cannot get back up to other fields. Once I enter an invalid entry, I am forced to enter something valid but then the edit screen flips to the next record to be entered. I tried using SET CONFIRM ON to no avail. Is there any way around this, short of rearranging the fields? I was just about to say you could rearrange the fields. But, seriously, if that isn't a viable option and your format screen is aesthetically perfect in its present form, you might want to modify your valid clause in your screen design. Under Edit options: Accept value when, add the following to your present condition: .OR. LASTKEY() = 5 What this does is to allow the pressing of the Up-arrow key as part of the validity check. That way, regardless of whether you've entered valid data in the last field, you can always go to previous fields. There is one drawback to this method. Once you've backed up to previous fields, leaving an invalid entry in the last field, you could easily skip to other records with the PgUp or PgDn key. These, of course, can be restricted from use by the use of ON KEY LABEL statements earlier in your program. If the extra work required for this key trapping seems too much, you might want to take my first suggestion, rearrange the fields. 3 Converting Text Files to Postscript Converting Text Files to PostScript Martin Leon Filtering output files through this utility allows them to print on a PostScript printer. Although the dBASE IV version 1.1 implementation of PostScript printer support is adequate for most needs, there is one situation where you will not be able to get your output to print on a PostScript printer. Many programmers often choose to create printed output by using SET DEVICE TO PRINT and @..SAY commands to produce the desired output. Although there is nothing wrong with this technique, it would take a lot of extra coding to get the same program to be able to print to a PostScript printer. The problem is twofold. First, when you use the @..SAY command to output to a printer, the dBASE IV print engine is bypassed and has no influence over the resulting output. Secondly, the PostScript printer is not expecting to receive lines of text to be printed, but rather is expecting a series of commands. With most printers, it doesn't matter if you bypass the dBASE IV print engine because as long as they receive a stream of ASCII characters (as with the @..SAY technique), they print the characters on the page as they were sent. However, if you do this with a PostScript printer, the printer tries to interpret what you're sending to it as instructional data rather than literal output. The end result is that the printer light blinks, indicating that the printer is processing your print job, but nothing is ever printed, leaving you tearing at your hair, banging on the top of the printer, rebooting your computer and a number of other human conditional responses to an uncooperative computer operation. The C program PostOut.EXE presented here provides a means for you to get around this obstacle. In order to understand how the program works we must first discuss how PostScript works and how it is that dBASE IV is able to print to a PostScript printer. What Is PostScript? PostScript is a very powerful page description language, hence its acceptance as a standard in the Desktop Publishing realm. It's a programming language that allows you to define in detail everything you want printed on a page. With it you can draw lines, boxes, circles, and other objects, fill them in with black, white or grayscale. It is also possible to define font attributes, print text, change the orientation of how things are printed on the page, and a host of other maneuverings. PostScript has changed the course of modern publishing, personalizing it in much the same way as the first PCs made home computing a possibility. PostScript comes complete with its own commands, operators, and syntax. Because of its extreme flexibility in laying out a page, it takes a pretty large program to print to a PostScript printer. Compared to what is required to print to a dot matrix it is a much more complex exchange of data. But consider the difference in quality. Fortunately for us, someone already put a lot of time and thought into designing such a program, making it not only possible but fairly transparent to the user for dBASE IV to print to PostScript printers. This program is provided in a file called the PostScript download file (PostScri.DLD). It defines several procedures (called macros) and maintains several memory variables to let the printer know which font to use, where on the page to put the next line of text, when a page is full, how to eject a page, and so on. None of these operations are handled in the same manner as they would be with conventional printers. To begin with, the ASCII control characters such as the form feed command (ASCII 12), carriage return (ASCII 13) and line feed (ASCII 10) are not interpreted by the PostScript printer to have the same effect as they do on other types of printers. In order to emulate the functionality of other printers, the PostScript driver PostScri.PR3 translates these characters into the names of macros that are defined in the download file. A form feed command, which is what tells most printers to advance the paper to the beginning of the next page, is translated into the name of a macro called "FF". This macro tells the PostScript printer to print everything that has been defined in printer memory up to this point, eject the page, and re-initialize several memory variables to be able to start defining the text for the next page to be printed. The combination of carriage return and line feed is translated into a macro called "CRLF". This macro tells the printer what is to be printed on the current line and to reset some more memory variables so that the printer knows where to start the next line of text. In PostScript, the command to put a line of text on the page is "show". The text you want printed should be surrounded by parentheses before the command. The macros and memory variables in the download file dictates at what coordinates to print everything. The DLD file also has macros for turning compressed print on and turning landscape printing on. Here's a small example of what it would take to print two lines using the download macros: Init <- initializes printer settings and memory variables Norm <- do not use any font attributes like bold or italic (ECHO OFF)show <- put the words ECHO OFF at current row/column coordinates CRLF <- change row/column coordinates to be able to print on next line (PROMPT $P$G)show CRLF FF <- print out everything that has been defined for current page and eject it and prepare the next page for printing Of course, this all depends on the download file being sent to the printer immediately before sending these commands. Without the download macros, it would take many, many more commands to accomplish the simplest output. About the Utility The C routine starting on page 10 uses this basic knowledge of PostScript and the dBASE IV PostScript download file to translate a standard ASCII file into a PostScript document. It contains switches to allow you to print using compressed print and/or landscape printing. You can set tab character spacing, page length, and choose one of three fonts. You can direct the PostScript output to any output device (LPT1, COM1, CON, PRN, and so on) or to another file. Now you can continue using @..SAY conventions, but, instead, you would redirect the printer output to a file. Once the file is created, this program will allow you to translate the file and send it to the desired destination along with the POSTSCRI.DLD file. After retrieving and verifying the command line arguments, POSTOUT simply looks for carriage return/line feed combinations, form feed characters and tab characters. Each line of text is prefaced with a "(" and terminated with ")show", enabling that line to be printed. Carriage return/line feeds are replaced with the CRLF macro, form feeds are replaced with the FF macro, and tab characters are replaced with the appropriate number of spaces. The program counts how many lines have been put on a page and issues a form feed macro when the specified maximum has been reached. Low order and high order ASCII characters are replaced by an asterisk since PostScript has a different symbol set for these characters. This means you will not be able to use box and line drawing. You can use PostOut for any ASCII text file. Suppose you're on a network and you want to print your AutoExec.BAT file to a PostScript printer assigned to LPT2 and print in compressed print Just have PostOut.EXE in your path as well as having the PostScri.DLD file in the same directory as POSTOUT, then issue the following command from the DOS level: POSTOUT AUTOEXEC.BAT LPT2 /C It's recommended you use the default font (Courier) because it is a fixed space font, like the one you see on the screen when you are in dBASE IV. If you use any of the other fonts, the text will not line up like it would on the screen. The other fonts would be used for readability or preferred appearance, but since they are proportional fonts (each letter has a different width), it would be difficult to get a document to line up the way you want it when using them. The program was created using MicroSoft Quick C and is not hardware dependent. You should be able to compile it using any C compiler and have it run on any machine. It does use the ANSI C standard function prototyping for the two functions that get command line arguments. If your C compiler does not conform to the ANSI standard, you will need to change the function headers for these two functions and change their function declarations. One last note: If you have trouble printing to your PostScript printer, whether it be from dBASE IV or using POSTOUT, try re-extracting the download (.DLD) file from the file DRIVERS.EXE. Sometimes people will use a text editor that puts an EOF character into the DLD file and this prevents it from working properly. To re-extract the file, go to your dBASE IV sub-directory and type in the command: DRIVERS -O POSTSCRI.DLD This will cause a fresh copy of the download file to be extracted, overwriting your existing one. PostOut.C /* POSTOUT Ä Uses dBase IV .DLD file to enable you to output a standard ASCII text file to a PostScript printer without modifying it. Prints 7 bit ASCII set. Higher ASCII characters are converted to *'s. Tabs are converted to spaces as specified by command line parameters. Created by Martin Leon, Ashton Tate Software Support Version 1.0a */ #include #include #include #include #define MAXPATH 256 FILE *infile; FILE *outfile; FILE *dldfile; /* ANSI C function prototypes */ int get slash arg( char switch letter, char *argv[] ); char *get eq arg( char prefix, char *argv[] ); /* Names of PostScript macros created in the DLD file */ char crlf[] = "CRLF\n"; char show[] = "show\n"; char ffeed[] = "FF \n"; char font[3][7] = {"1FONT\n","2FONT\n","3FONT\n"}; /* Other variables */ char tab[11] = " "; /* tab set to max of 10 spaces */ char homedir[MAXPATH]; char temp[MAXPATH]; char *ptr; int linecount = 0; int max lines = 0; int c = '\0'; int x = 0; int tabspace; int fontnumber = 0; int in parens = 0; int main( int argc, char *argv[] ) { /* Check for at least 2 command line arguments */ if( argc < 3 ) { printf( "\nPOSTOUT.EXE v1.0a\n\n"\ "Input and output file name required. E.G.: POSTOUT IN.TXT OUT.TXT\n"\ "Output file can be any output device or file name.\n"\ "Optional switches (can be used in any order/combination):\n"\ " /C for compressed print\n"\ " /L for landscape printing\n"\ " T= for tab stop spacing \n"\ " F= for font number\n"\ " 1 is Courier, fixed spacing \n"\ " 2 is Helvetica, proportional spacing\n"\ " 3 is Times, proportional spacing\n"\ " L= for page length \n"); return 1; } /* Check for tab setting switch and adjust tab spacing if needed */ tabspace = atoi( get eq arg( 'T', argv ) ); if( tabspace < 3 || tabspace > 10 ) { if( tabspace ) { printf( "\nTab setting must between 3 and 10\n" ); return 1; } } if( tabspace ) { ptr = tab; ptr += strlen( tab ); while( (int)strlen( tab ) > tabspace ) { ptrÄ; *ptr = '\0'; } } else strcpy( tab ," " ); /* Check for font setting switch and set fontnumber */ fontnumber = atoi( get eq arg( 'F', argv ) ); if( fontnumber > 3 ) { printf( "\nFont number must be between 1 and 3\n" ); return 1; } /* Check for page length setting and adjust if needed */ max lines = atoi( get eq arg( 'L', argv ) ); if( max lines < 45 || max lines > 81 ) { if( !max lines ) if( get slash arg( 'L', argv ) ) max lines = 45; else max lines = 63; else { printf( "\nPage length must be between 45 and 63\n" ); return 1; } } /* Make sure input file can be opened */ if( ( infile = fopen( argv[1], "rt" ) ) == NULL ) { printf( "\nCannot open %s for input.\n", argv[1] ); fcloseall(); return 1; } /* Make sure output file can be opened */ if( ( outfile = fopen( argv[2], "wt" ) ) == NULL ) { printf( "\nCannot open %s for output.\n", argv[2] ); fcloseall(); return 1; } /* Determine directory POSTOUT was called from and add DLD file name to it*/ /* This method will only work with DOS version 3.0 or higher */ strcpy( homedir, argv[0] ); ptr = homedir; ptr += (strlen( homedir ) - 1 ); while( *ptr != '\\' ) ptrÄ; strcpy( ++ptr, "POSTSCRI.DLD" ); /* Make sure dld file can be openned from same dir as POSTOUT.EXE */ if( ( dldfile = fopen( homedir, "rt" ) ) == NULL ) { printf( "\nCannot open %s\n"\ "DLD file must be in same directory as POSTOUT.EXE\n", homedir ); fcloseall(); return 1; } /* copy dld file to output */ printf( "\nWriting Output\r" ); while( (c = fgetc( dldfile )) != EOF ) fputc( c, outfile ); /* If you are not using an HP III with a PostScript cartridge, add comment delimiters to next line */ fputs( "/tlxoff 18 def \n", outfile ); /* Insert call in output to DLD Init macro to initialize printjob */ fputs( "Init\n", outfile ); /* If /L command line argument was used, set to landscape printing */ if( get slash arg( 'L', argv ) ) fputs( "LAND\n", outfile ); /* If /C command line argument was used, set to compressed printing otherwise set to normal printing */ if( get slash arg( 'C', argv ) ) fputs( "Cmp+\n", outfile ); else fputs( "Norm\n", outfile ); /* Send font selected or set to default of first font */ if( fontnumber != 0 ) fputs( font[ fontnumber - 1 ], outfile ); else fputs( font[0], outfile ); /* Format text for PostScript and send to output */ fputc( '(', outfile ); in parens = 1; while( ( c = fgetc( infile ) ) != EOF ) { /* Translate form feed characters */ if( c == '\x0C' ) { if( in parens ) { fputc( ')', outfile ); in parens = 0; fputs( show, outfile ); } fputs( ffeed, outfile ); linecount = 0; fputc( '(', outfile ); in parens = 1; continue; } /* Replace carraige returns with CRLF macro in DLD */ if( c == '\n' ) { /* If at last line on page, call FF macro in DLD */ if( ++linecount == max lines ) { if( in parens ) { fputc( ')', outfile ); in parens = 0; } fputs( show, outfile ); fputs( ffeed, outfile ); fputc( '(', outfile ); in parens = 1; linecount = 0; continue; } /* Close parens on SHOW macro in DLD and add CRLF macro */ if( in parens ) { fputc( ')', outfile ); in parens = 0; } fputs( show, outfile ); fputs( crlf, outfile ); fputc( '(', outfile ); in parens = 1; continue; } /* Replace some control character and all ASCII characters with values greater than 128 in input with an asterisk */ if( c > '\x7F' || c < '\t' || (c < ' ' && c > '\n') ) { if( c == '\t' ) fputs( tab, outfile ); else fputc( '*', outfile ); continue; } /* Replace tab character in input with number of spaces determined by command line argument */ if( c == '\t' ) { fputs( tab, outfile ); continue; } /* Replace backslashes and parens in input to \\ and \( and \) */ if( c == '\\' || c == '(' || c == ')' ) fputc( '\\', outfile ); fputc( c, outfile ); } /* End of file reached Ä close parens for SHOW macro */ fputc( ')', outfile ); fputs( show, outfile ); /* Send FF for macro in DLD */ fputs( ffeed, outfile ); /* Send EOT character to indicate end of job */ fputc( '\x04', outfile ); /* Close files */ fcloseall(); printf( "File Written to %s\n", argv[2] ); return 0; } /* Scans all command line arguments for one that begins with a specific letter and is followed by an = sign. Returns pointer to first character after the equal sign */ char *get eq arg( char prefix, char *argv[] ) /* ANSI C function header */ { char *temptr = *argv; while( *argv ) { char *ptr = strcpy( temp, *argv ); temptr = ptr; if( toupper( *ptr++ ) == prefix && *ptr++ == '=' ) return ptr; argv++; } temptr = (char *)argv; return temptr; } /* Scans all command line arguments for one that begins with a slash and is followed by a specific letter. Returns 1 if switch is found in arguments */ int get slash arg( char switch letter, char *argv[] ) /*ANSI C function header*/ { while( *argv ) { char *ptr = strcpy( temp, *argv ); if( *ptr == '/' && toupper( *(++ptr) ) == switch letter ) return 1; argv++; } return 0; } /* EOF: PostOut.C */ 4 Popup Combinations to Go Popup Combinations To Go Adam Menkes Simple to seemingly impossible popups. Many people would like the ability to have multiple field popups and conditional popups in their applications. There are several ways to do this using pseudo-popups such as a BROWSE NOEDIT NOAPPEND NODELETE or using arrays and painting the screen. This article will progress from showing you how to do a simple popup a few different ways to doing multi-field popups with fields from several databases and have a seemingly impossible conditional multiple multi-field popup active. You will also learn how to achieve an @.GET inside a POPUP (for both adding and editing), and various other tips, tricks, and techniques that show that there is more than one way to SCAN a CAT. Hey, how about that? I could always get a job as a comedian if this programming thing doesn't pan out. The basic concept of defining a popup lies in how to define the selections within the popup. This is not as obvious as it seems. You can DEFINE POPUP PROMPT FIELD but not DEFINE POPUP PROMPT FIELDS , , .. But what if you could store Fields 1 to n to a character variable, and define that as the BAR of the POPUP? To further explore this concept, consider the database structures below: Given these database structures, and assuming the PHONE1 database is active, the following is the definition for a simple POPUP of last names : DEFINE POPUP SimplePop FROM 1, 40 PROMPT FIELD Lname Alternatively, the same POPUP can be defined this way : DEFINE POPUP ScanPop FROM 1, 40 X = 1 SCAN DEFINE BAR X of ScanPop PROMPT Lname X = X + 1 ENDSCAN Now, suppose you wanted a conditional POPUP that displayed a listing of either the last name or, there was no last name, the name of the company. All you need do is change the DEFINE BAR in the above SCAN/ENDSCAN to read: DEFINE BAR X of ScanPop PROMPT ; IIF(LEN(TRIM(CoName)) = 0, Lname, CoName) For multiple field popups : DEFINE BAR X of ScanPop PROMPT ; TRIM(Fname) + " " + TRIM(Lname) + " " + DTOC(DateIn) Suppose Phone1.DBF is open in work area 1 and Phone2.DBF is open in work area 2 (SET RELATION TO Key INTO B) and you want a multi-field popup using fields from both databases : DEFINE BAR X of ScanPop PROMPT ; TRIM(Fname) + " " + TRIM(Lname) + " " + B->Phone This will only select the first match for the Phone number in area 2 for each KEY in area 1. Later, you will see how to get them all. Assume you wanted a POPUP with "header" and "footer" information. The information can be as many lines and contain any text and fields you wish : DEFINE BAR 1 of ScanPop PROMPT "Any Header Info You Wish" SKIP DEFINE BAR 2 of ScanPop PROMPT "Line 2 Info " + AnyFields SKIP x = 3 && New starting BAR number. SCAN ... DEFINE BAR X OF ... x = x + 1 ... ENDSCAN DEFINE BAR X of ScanPop PROMPT "Any Footer Info You Wish" SKIP DEFINE BAR X + 1 of ScanPop PROMPT "Line 2 " + AnyFields SKIP ... The SKIP is optional and is used in this instance so that the header and footer information is not selectable. AnyFields can be any field or combination of fields from one or more databases in any work area. Another method of defining a pick list is to DEFINE a MENU, and DEFINE PADs within that menu. Normally, a MENU is thought of as a horizontal bar menu. Since positioning is determined by the programmer, you can DEFINE PADs within the MENU vertically (or randomly if so desired). Using the same principals discussed earlier with ScanPop, below is the code for a simple picklist using a MENU instead of a POPUP. The result looks identical to a popup but functions somewhat differently: DEFINE MENU ScanMenu x = "1" && Note the quotes ("). SCAN DEFINE PAD Pad&x of ScanMenu PROMPT Lname AT VAL(x), 40 *- Note: The 40 can be any column position. x = LTRIM(STR(VAL(x) + 1)) *- Increment a number (x) stored as a character variable. ENDSCAN How is this any different from defining a POPUP? Well, a menu can not be defined beyond the screen length, so you are limited to a picklist of 25 items (43 when your display is set to EGA43 mode), or are you? As the DEFINE PAD also optionally uses the AT parameter to specify a column position, and the row is defined as a variable (VAL(x)), the column can also be defined as a variable, which in effect, gives you a multi-column picklist, limited by the width of the PADs times the number of PADs vertically times the number of PADs horizontally and also depends upon available memory. You could also define the PADs AT MOD(VAL(x), 24) or MOD(VAL(x), 42) in 43 line mode (1 less than the screen length as the message line will blank the item) so that the picklist remains in one column. DEFINE MENU ScanMenu x = "1" && Note that x is character type. y = 1 && Added for muli-column display. SCAN DEFINE PAD Pad&x of ScanMenu PROMPT Lname ; AT MOD(VAL(x), 24), y x = LTRIM(STR(VAL(x) + 1)) *- Increment a number (x) stored as a character variable. IF MOD(VAL(x), 24) = 0 y = y + 17 * Increment the column position by 17 (or whatever number * of column positions to display the next portion of the * picklist.) ENDIF ENDSCAN Since row and column positions are defined as variables, you can see how you could easily define a menu where the items were defined to be visually more interesting, such as defining the PADs at every other row, or defining them in a step fashion. Using the above program code, simply change: ... AT MOD(VAL(x), 24 / 2) * 2, y && Every other row. or more generally : ... AT MOD(VAL(x), 24 / n) * n, y && Every nth row. To step, incrementing one column position per defined bar: ... AT MOD(VAL(x), 24), y + VAL(x) or ... AT MOD(VAL(x), 24), y + MOD(VAL(x), 24) IF y >= variable column position y = y + number of spaces desired ENDIF *- Increments by 1 column position and y spaces between records 1-25 and records 26-50 etc. Now, using this principal of variable row and column coordinates, you can see how the following menus are defined. You can do "V", "X", "<", ">", "\", "/" rather easily and, in fact, could do any pattern or randomly if so desired. To get one or more ">" patterns, you need to reverse the direction of the step at the midpoint; line 12 in 25 line mode (lines 0 - 24), and line 21 in 43 line mode (0-42). PAD1 is at row 0 (the PAD number is always 1 higher than the row) and the easiest way to have symmetry about a midpoint (line 12 in this example) is to start with the value of the midpoint (12) and subtract (or add) the appropriate number of rows. Using the absolute value of the difference between the current row and the midpoint will provide this symmetry as 11 - 11 = 0, 11 - 10 = 1, and 11 - 12 = - 1 [ABS(-1) = 1]. Here is the syntax for the ">" pattern: DEFINE PAD pad&PadNum OF MenuPad AT MOD(VAL(PadNum), 24), ; mCol + abs(12 + (1 - (INT(VAL(PadNum) / 24)) ; - ABS(11 - MOD(VAL(PadNum), 24)))) PROMPT ; " (" + AC + ") " + Phone + " " To reverse the direction: DEFINE PAD pad&PadNum OF MenuPad AT MOD(VAL(PadNum), 24), ; mCol + ABS(12 + (1 - (INT(VAL(PadNum) / 24)) ; + ABS(11 - MOD(VAL(PadNum), 24)))) PROMPT ; " (" + AC + ") " + Phone + " " Can you spot the difference in the code? There is only one : Line 3 has a minus sign for the "<" pattern and a plus sign for the ">" pattern. Surely there must be an easier and better way to determine direction without duplicating code. The wonders of macro substitution allow not only character substitution but commands and operators as well. Here's a way of performing the pattern in either direction: DEFINE PAD pad&PadNum OF MenuPad AT MOD(VAL(PadNum), 24), ; mCol + ABS(12 + (1 - (INT(VAL(PadNum) / 24)) ; &PlusMinus. ABS(11 - MOD(VAL(PadNum), 24)))) PROMPT ; " (" + AC + ") " + Phone + " " * - PlusMinus is a character variable equal to "+" or "-" Now, the "><" pattern is easy. In the SCAN..ENDSCAN loop, add : PlusMinus = IIF(mCol < 17, "-", "+") * 17 is the length of the PROMPT in this example. These are just a few examples of the limitless possibilities of defining POPUPs and MENUs using SCAN..ENDSCAN with proper variable substitution of the row and column positions. Now that you have these concepts wired (hopefully without too much short-circuiting), you can probably figure out how to have two "popups" active, regardless of whether or not they are multi-field or multi-relational popups. Since you know from the documentation that two popups can not be active at the same time, but a MENU and a POPUP can, and since either one can be defined using SCAN / ENDSCAN with any fields from any related databases FOR any condition (or indexed conditionally), all you need to do is activate whichever popup coincides with each PAD. Rather than determining this programmatically, you initialize each POPUP as you are defining your MENU PADs. Each one of these popups will be activated by cursor movement keys (left-arrow, right-arrow, up-arrow, down-arrow, Home, End) if ON PAD is used, and if ON SELECTION PAD is used, the Enter key. Below is the code for having both popups (actually one menu that looks like a popup and many popups of which only one is active at a time). As you can see, there are two SCAN / ENDSCAN loops, one inside the other, which define your selections. USE Phone1 IN 1 ORDER Lname USE Phone2 IN 2 ORDER Phone DEFINE MENU MenuTest SELECT 1 x= "1" SCAN mKey = Key && Related field in both databases. DEFINE PAD Pad&x of MenuTest PROMPT ; Lname + ", "+ Fname AT VAL(x), 10 SELECT 2 DEFINE POPUP Pop&x FROM VAL(X), 52 y = 0 && No records may be selected. SCAN for Key = mKey *- If a matching record is FOUND(), increment the BARs, *- DEFINE them, and initialize the ON SELECTION. y = y + 1 DEFINE BAR Y OF Pop&x PROMPT "(" + AC + ")"+Phone ON SELECTION POPUP Pop&x DEACTIVATE MENU ENDSCAN IF y > 0 *- If records ARE selected - ON PAD Pad&x OF MenuTest ACTIVATE POPUP Pop&x *- ACTIVATE the appropriate POPUP. ENDIF x = LTRIM(STR(VAL(x) + 1)) *- Increment a character numeric variable ("1" not 1). SELECT 1 ENDSCAN ACTIVATE MENU MenuTest Again, keep in mind that the more pads, bars and popups defined, the more potential for "Insufficient memory" error messages. Other things you can do with POPUPs include having memo fields in the POPUP and data input or edited directly (or seemingly so) in the POPUP. These capabilities are actually quite simple. In order to GET a variable inside a POPUP, you first DEFINE the POPUP as you normally would, then use ON SELECTION POPUP to DEACTIVATE the POPUP, immediately afterwards SHOW the POPUP, and GET the variable at a variable row and column position. A partial example is listed below : SEEK mKey *Ä mKey should already be initialized - i.e. mKey = "000001" DEFINE POPUP PopPh FROM 14, 4 DEFINE BAR 1 OF PopPh PROMPT SPACE(20) + "" DEFINE BAR 2 OF PopPh PROMPT REPLICATE(CHR(205), 70) SKIP x = 3 SCAN WHILE KEY = mKey DEFINE BAR x OF PopPh PROMPT " (" + AC + ") " + Phone + ; " x " + Ext + " " + PhDesc + " " + TimeSt + " " + TimEnd x = x + 1 ENDSCAN ON SELECTION POPUP PopPh DEACTIVATE POPUP ACTIVATE POPUP PopPh MROW = ROW() IF BAR() <> 1 mEdRec = BAR() - 2 ELSE mEdRec = 1 ENDIF SHOW POPUP PopPh *Ä This is where you would insert code to determine what *Ä record was selected and either store blanks (zeros) or *Ä the contents of the selected record to the memory vars. IF mEdRec = 1 * - Set memory variables blank ELSE * - Set memory variables equal to the value of respective fields ENDIF SEEK mKey *Ä Reposition the record pointer to the first matching Key. IF FOUND() SKIP mEdRec - 1 *Ä Skip the appropriate number of records. ENDIF IF LASTKEY() <> 27 && Esc key was not pressed. @ mRow, 6 SAY "(" @ mRow, 7 GET mAC PICTURE "999" @ mRow, 10 SAY ")" @ mRow, 12 GET mPhone PICTURE "999-9999" @ mRow, 20 SAY " x " @ mRow, 23 GET mExt PICTURE "99999" @ mRow, 29 GET mPhDesc PICTURE "XXXXXXXXXXXXXXXXXXXXXXXXX" @ mRow, 55 GET mTimeSt PICTURE "99:99!M" @ mRow, 64 GET mTimEnd PICTURE "99:99!M" READ ENDIF 5 Etc. Etc. Performance Considerations The question of dBASE IV performance has run the gamut from lost clusters and available memory to solar flares and a shift in the space time continuum. But just how do you explain an operation that runs slower now than it did in 1.0? The Need For Speed Some users have informed us of speed decrease during REPLACE operations with dBASE IV version 1.1. The same operation in dBASE IV version 1.0 was faster. This may be due to delays caused by overlay swapping. This can be the case when REPLACEing or APPENDing to indexed databases. True, both versions had .MDX files but indexes are not the cause of the problem. In version 1.1, the INDEX and REPLACE operations are in different segments of the very large DBASE.OVL file, thus causing a good deal of swapping in and out of memory. This is what causes the slow down. Here are some tips for speeding up performance in the new 1.1 environment: ù Set up a RAM disk and SET DBTMP (a DOS environmental variable) equal to this RAM disk directory. ù Free up as much DOS conventional memory as possible, getting rid of things such as ANSI.SYS (which most people don't need) in your CONFIG.SYS file. Remove other non-essential drivers from your CONFIG.SYS as well. If you're using a 4.x version of DOS, remove the DOS APPEND command from your AUTOEXEC.BAT file. ù For Novell users, get the Novell EMSNET3 and XMSNET3. These will free up more DOS conventional memory by loading NET3 in extended or expanded memory. The NetWire forum on CompuServe should have these utilities available. You may also call Novell at 1-800-LANSWER. ù Play "musical indexes". Create an .MDX file called Empty with a tag of the same name. To do this, put any database into use and type in the command: INDEX ON " " FOR .F. TAG Empty OF Empty Prior to doing a large REPLACE, close the .DBF and rename its production .MDX to another name. Rename Empty.MDX to make it your temporary production .MDX. Use the database and perform your REPLACE or APPEND operations. Close the database, rename the two .MDX files back to their original names and REINDEX. Here is a series of commands that outline the operation, using a file called Trans.DBF, which is assumed to be open: USE && closes file RENAME Trans.MDX TO Hold.MDX RENAME Empty.MDX TO Trans.MDX USE Trans REPLACE ... If all of the REPLACEs are to be done on a single field, the process is much simpler. A tag for a single field can be updated by the following command: SET ORDER TO REPLACE NEXT 1 WITH This last suggestion adds some code to your program, but it will work around the slowness you're experiencing. Banyan BIOS Although there are a small number of problems known to exist when running dBASE IV with Banyan Vines 3.1 and 4.0, they are attributed to the lack of full Net BIOS support in the software. However, it is possible to setup the Banyan network to fully support the NetBios protocol. All known problems will disappear once the Banyan network is setup properly for NetBios support. There are three steps to setting up Banyan with the full NetBios support: 1. Create a "NetBios" Service on the server by running the Banyan MSERVICE program. This program sets up NetBios emulation protocol across the LAN and checks for unique node addresses. 2. Run the PCNETB.COM program on the workstation. This program loads the NetBios protocol. 3. Run the SETNETB.COM program on the workstation. This program assigns a name to the workstation. Adding the NetBios support will up take about 37KB of memory on top of what is already loaded. For example, on the server, run the MSERVICE program and set the NetBios service name to something like "NETB@Exec@OURCOMP". On the workstation, add the following to the Autoexec.BAT: PCNETB SETNETB /N:nodename NETB@Exec@OURCOMP where nodename is your unique workstation name. To be able to set NETBIOS for Vines 4.01 you need to load the TSR using the syntax below. "SETNETB /NAME:nodename NETB@Exec@OURCOMP" One final observation: this TSR can't be loaded high. 6 Corrigenda Corrigenda In our February 1991 issue, the code included in the article "Easy .BINs in C" contained some errors related to the placement of underscores and dashes. Please note that in any case where the global variables argc or argv are mentioned, each should be immediately preceded by an underscore, thus appearing as _argc or _argv. Additionally, the last code line in Reverse!.C shows the _argc variable being followed by a minus sign. The line should appear as strrev(_argv[_argc--]); The same scenario is applied in DD.C on page 18. In the last clause, the double minus sign was again omitted on two lines. The code should actually read if (negative) string[--len] = '-'; while (len) string[--len] = ' '; 7 Desktop Calculator Desktop Calculator Dan Madoni Isn't it incredible how rapidly things change? It wasn't that long ago when the idea of personal computing was not nearly as matter-of-fact as we regard the activity today. Seems like it was around that same time that the throw-away calculator that you get with a subscription to Time used to cost a pretty penny. I am always amused to walk into the office of a power executive and find that same throw-away calculator sitting next to a new 33MHz Super Dynamo 386 computer. What's the point? Well, maybe the calculator is used to calculate the ultimate numbers to be entered into a database. Wouldn't it be great to be able to pop up a "video calculator" that does all that basic arithmetic?? The Calc() function (see listing) does just that. Suppose you're sitting in a numeric field, and the only thing keeping you from punching in the numbers is the fact that you don't know which numbers to punch Ä they haven't been calculated. You press a function key and up pops this calculator on the screen that works the same way as the pocket calculator on your desk (or in your pocket.) You punch in the numbers on the video calculator via the number pad on your keyboard and watch the result appear after each calculation on the LED-style display. Pressing Escape erases the calculator and returns you to your prior activity, which is left unaffected. After copying the Calc() UDF code below into your program, include the following command in your program: ON KEY LABEL F5 ?? Calc() Whenever you need to use the calculator, press F5, and it will appear in the middle of the screen. Use the number pad for numbers. All your operators for addition, subtraction, multiplication and division are on the keypad of most computer keyboards as well. The asterisk is used for multiplication rather than an X. All other operator symbols should be familiar. You use the Enter key to get the result, taking the place of the equal sign. When you are in the process of doing calculations, the number you are working with is accumulative until you press the CLEAR button, ("C" on the keyboard.) If you make a mistake while entering a number, use the left-arrow key to delete the right-most number. Of course, you can use another key other than F5. The function key F5 happens to be a key that does not disable other full-screen functions. Function: Calc FUNCTION Calc *Ä Record current environment. B4color = SET("ATTRIBUTES") B4curs = SET("CURSOR") B4dec = SET("DECIMAL") SET CURSOR OFF SET DECIMAL TO 2 SAVE SCREEN TO B4calc tempclr = SET("ATTRIBUTES") *Ä Draw the calculator. SET COLOR TO N/W @ 8,32 FILL TO 18,50 COLOR W/N @ 7,31 CLEAR TO 17,49 SET COLOR TO &tempclr *Ä "Buttons" for calculator. @ 10,33 SAY " 7 " COLOR W+/N @ 10,37 SAY " 8 " COLOR W+/N @ 10,41 SAY " 9 " COLOR W+/N @ 10,45 SAY " + " COLOR Gr+/N @ 12,33 SAY " 4 " COLOR W+/N @ 12,37 SAY " 5 " COLOR W+/N @ 12,41 SAY " 6 " COLOR W+/N @ 12,45 SAY " - " COLOR Gr+/N @ 14,33 SAY " 1 " COLOR W+/N @ 14,37 SAY " 2 " COLOR W+/N @ 14,41 SAY " 3 " COLOR W+/N @ 14,45 SAY " X " COLOR Gr+/N @ 16,33 SAY " 0 " COLOR W+/N @ 16,37 SAY " " + CHR(249) + " " COLOR Gr+/N @ 16,41 SAY " C " COLOR Gr+/N @ 16,45 SAY " " + CHR(246) + " " COLOR Gr+/N @ 8,33 SAY SPACE(15) COLOR W/N SAVE SCREEN TO curcalc DECLARE calcnums[100] && Numbers stored as they are pressed DECLARE calcops[100] && Operators stored as they are pressed STORE .F. TO decon,calcdone,invkey *Ä decon : Turn decimal point on or off *Ä calcdone : Tells whether or not to keyboard a value after * calculations have been made *Ä invkey : Indicates Invalid Key Pressed if .T. newnum = .T. && Indicates new number is on the display worknum = "0" && Number to be displayed calccnt = 0 && Number of entries made before "=" is pressed DO WHILE .T. *Ä If the user has not pressed an invalid key... IF .NOT. invkey worknum = IIF(LEN(worknum) = 0, "0", worknum) worknum = IIF(LEN(worknum) > 1 .AND. SUBSTR(worknum,1, 1) = "0",; SUBSTR(worknum, 2), worknum) * Remove leading zero worknum = IIF(LEN(worknum) > 13, SUBSTR(worknum, 1, 13), worknum) * Truncate long number RESTORE SCREEN FROM curcalc @ 8,35 SAY SPACE(13 - LEN(worknum)) + worknum COLOR R+/N && Show number ELSE invkey = .F. ENDIF waiting = INKEY(0) *Ä Reset if new number IF newnum worknum = "" newnum = .F. decon = .F. ENDIF DO CASE CASE waiting = 27 .OR. waiting = 23 && ESCape or Ctrl-End EXIT CASE waiting = 19 && Left-Arrow @ 8,34 SAY CHR(237) COLOR Gr+/N IF LEN(worknum) <> 0 decon = IIF(SUBSTR(worknum,LEN(worknum), 1) = ".", .F., .T.) * Turn decimal off if deleted worknum = SUBSTR(worknum,1,LEN(worknum) - 1) && truncate number ENDIF *Ä Numbers on keypad CASE waiting = 55 @ 10,33 SAY " 7 " COLOR W+/G worknum = worknum + "7" CASE waiting = 56 @ 10,37 SAY " 8 " COLOR W+/G worknum = worknum + "8" CASE waiting = 57 @ 10,41 SAY " 9 " COLOR W+/G worknum = worknum + "9" CASE waiting = 52 @ 12,33 SAY " 4 " COLOR W+/G worknum = worknum + "4" CASE waiting = 53 @ 12,37 SAY " 5 " COLOR W+/G worknum = worknum + "5" CASE waiting = 54 @ 12,41 SAY " 6 " COLOR W+/G worknum = worknum + "6" CASE waiting = 49 @ 14,33 SAY " 1 " COLOR W+/G worknum = worknum + "1" CASE waiting = 50 @ 14,37 SAY " 2 " COLOR W+/G worknum = worknum + "2" CASE waiting = 51 @ 14,41 SAY " 3 " COLOR W+/G worknum = worknum + "3" CASE waiting = 48 @ 16,33 SAY " 0 " COLOR W+/G worknum = worknum + "0" CASE waiting = 46 .AND. .NOT. Decon && Decimal point @ 16,37 SAY " " + CHR(249) + " " COLOR W+/G worknum = worknum + "." decon = .T. CASE STR(waiting,3) $ " 43 13 61" && ENTER, "=", or "+" @ 10,45 SAY " + " COLOR W+/G newnum = .T. calccnt = calccnt + 1 calcnums[calccnt] = VAL(worknum) calcops[calccnt + 1] = "+" CASE waiting = 45 @ 12,45 SAY " - " COLOR W+/G newnum = .T. calccnt = calccnt + 1 calcnums[calccnt] = VAL(worknum) calcops[calccnt + 1] = "-" CASE CHR(waiting) $ "XX*" @ 14,45 SAY " X " COLOR W+/G newnum = .T. calccnt = calccnt + 1 calcnums[calccnt] = VAL(worknum) calcops[calccnt + 1] = "*" CASE waiting = 47 @ 16,45 SAY " " + CHR(246) + " " COLOR W+/G newnum = .T. calccnt = calccnt + 1 calcnums[calccnt] = VAL(worknum) calcops[calccnt + 1] = "/" CASE CHR(waiting) $ "Cc" && The CLEAR button calccnt = 0 newnum = .T. OTHERWISE invkey = .T. && User pressed an invalid key ENDCASE IF CHR(waiting) $ "+/-*XX=" .OR. waiting = 13 allnum = calcnums[1] && allnum will be the result of all calculations calccnt2 = 1 *Ä The following DO WHILE loop takes all of the numbers and *Ä operators that have been accumulated thus far and *Ä calulates them in the order entered. We can't *Ä concantenate them into one large string to calculate because *Ä of the way that dBASE IV orders the precedence of operators. *Ä (such as 5 + 5 / 2 calculated from left to right would result *Ä differently than as one dBASE expression) DO WHILE calccnt2 < calccnt calccnt2 = calccnt2 + 1 DO CASE CASE calcops[calccnt2] = "+" allnum = allnum + calcnums[calccnt2] CASE calcops[calccnt2] = "-" allnum = allnum - calcnums[calccnt2] CASE calcops[calccnt2] = "*" allnum = allnum * calcnums[calccnt2] CASE calcops[calccnt2] = "/" allnum = allnum / calcnums[calccnt2] ENDCASE ENDDO @ 8,33 SAY SPACE(15) COLOR W/N @ 8,35 SAY STR(allnum,13,2) COLOR R+/N && Show the result STORE .T. TO calcdone,newnum waiting = INKEY(0) KEYBOARD CHR(waiting) && Keyboard next number and clear for new number ENDIF IF .NOT. calcdone && If number is still building on display IF .NOT. invkey SET BELL TO 5000,1 && Good tone if key is not invalid ELSE SET BELL TO 20,1 && Bad tone if key is invalid ENDIF ?? CHR(7) ELSE calcdone = .F. invkey = IIF(.NOT. CHR(waiting) $ "1234567890+-Cc*/",.T., invkey) * Determine valid key pressed ENDIF ENDDO RESTORE SCREEN FROM B4calc RELEASE SCREEN B4calc RELEASE SCREEN curcalc RELEASE calcnums,calcops SET COLOR TO &B4color SET DECIMAL TO B4dec SET CURSOR &B4curs RETURN .T. * EOF: Calc.PRG 8 Betcha Can't Pick Just One Betcha Can't Pick Just One Lena Tjandra Popups are a welcome addition to the functionality of the dBASE language. By their nature, once you press Return on a highlighted selection in the popup, it disappears and an action is taken. You cannot make more than one selection at a time, but there is an alternate and sometimes preferable way of utilizing a popup. Suppose you have a list of salespersons, and you would like to generate a year-to-date sales report on specific salespersons only. You'd ideally like to go through a popup list, pressing Return (or some other key to mark the records) on those you want selected for a certain operation. When you are done, a specific exit key would clear the popup and perform your task. How would you do this and still incorporate the use of a popup? The program that follows shows you how. The purpose of this program is to create a popup from which the user can make multiple selections. The popup is created from values stored in a database. When the user makes a selection, the popup is redefined with a character marker that indicates a selection. At the same time, a logical true (.T.) value is replaced into the field, "Selected" which corresponds to the selected option in the database. If an option on the popup is de-selected, then a logical false (.F.) value is replaced into the "Selected" field and the popup is redefined without the character marker. If your popup contains a lot of options to choose from, then your best bet is to store them in a database and create the popup based on the values in that database. There are several good reasons for doing this such as code brevity, modularity, and clarity. For instance, instead of having to DEFINE BARs for each option in the popup, you can define all the BARs in just a few statements as shown on lines 19 - 22. Second, it makes the program more modular because now you can modify the options through the database rather than through the program, thus, giving your program more flexibility. Third, you can create an additional field in the database to store marked selections, thus, saving you from declaring an array of unnecessary size. Furthermore, it makes the goal easier to process. Suppose you stored selected values in an array. The consequence would be having to look in each element to see which options have been selected and create a macro expression for the report. If the values were marked in the database by assigning .T. to a field called SELECTED, then a single command will suffice: REPORT FORM sales FOR selected I would also like to point out a few tips contained in this program. You may wonder why a SHOW POPUP on line 35 is issued before an ACTIVATE POPUP. For those of you who have worked with popups a lot, you may notice that the popup will flicker very quickly when control is returned to the ACTIVATE POPUP statement or when the popup is re-activated. By issuing a SHOW POPUP, we can alleviate the flickers. Lines 53 - 54 may seem peculiar, but since the database has an active index, we can only move to the record relative to the top of the database. However, if the database were in natural order, we can reference it directly with a GOTO statement. Another method is to do a SEEK, but doing a SKIP is the only way that works with both an indexed or un-indexed database. Additionally, you may also notice that the F9 and F10 keys have been defined as hotkeys to allow the user the choice of selecting or de-selecting all the options on the popup at once. This brings me to one final point. In the procedure entitled PSelect, the bar is redefined, but it is not necessary to deactivate and reactivate the popup. Since this procedure was called from an ON SELECTION POPUP, the ON SELECTION takes care of reactivating the popup with the current bar definitions. On the other hand, the procedure Sel All was called from an ON KEY, so we need to deactivate the popup manually as on line 77, and reactivate it again in order for the new bars to display. 1 CLEAR 2 SET STATUS OFF 3 SET TALK OFF 4 5 *Ä Store ASCII value of {down-arrow} and {Esc} to variables 6 dnarrow = CHR(24) 7 escape = 27 8 9 *Ä Open database 10 SELECT 1 11 USE salesrep ORDER name 12 13 *Ä Define popup, pop1 14 DEFINE POPUP pop1 FROM 5, 15 TO 23, LEN(name) + 15 15 16 bar cnt = 1 17 18 *Ä Define bars of popup from the "name" field in database 19 SCAN 20 DEFINE BAR bar cnt OF pop1 PROMPT TRIM(Name) + IIF(selected, CHR(17), "") 21 bar cnt = bar cnt + 1 22 ENDSCAN 23 24 *Ä Define popup action 25 ON SELECTION POPUP pop1 DO PSelect 26 27 *Ä Define hotkey for selecting all options 28 ON KEY LABEL F10 DO Sel All WITH .T. 29 30 *Ä Define hotkey for de-selecting all options 31 ON KEY LABEL F9 DO Sel All WITH .F. 32 33 *Ä Activate popup 34 DO WHILE LASTKEY() <> escape 35 SHOW POPUP pop1 36 ACTIVATE POPUP pop1 37 ENDDO 38 39 CLEAR 40 CLEAR ALL 41 SET STATUS ON 42 SET TALK ON 43 44 45 PROCEDURE pselect 46 47 *Ä Redefine bar based on selection/de-selection 48 DEFINE BAR BAR() OF pop1 PROMPT IIF(RIGHT(PROMPT(), 1) = CHR(17),; 49 SUBSTR(PROMPT(), 1, AT(CHR(17), PROMPT()) - 1), PROMPT() + CHR(17)) 50 51 *Ä Change the "Selected" field to .T. if option has been selected 52 *Ä or .F. if option has been de-selected. 53 GO TOP 54 SKIP BAR() - 1 55 REPLACE selected WITH IIF(selected, .F., .T.) 56 57 *Ä Move highlight in popup down to the next option 58 KEYBOARD dnarrow 59 60 RETURN 61 62 PROCEDURE Sel All 63 64 PARAMETER s key 65 66 *Ä Change all values in the "Selected" field .T. or .F. in database 67 REPLACE ALL Selected WITH s key 68 69 bar cnt = 1 70 71 *Ä Re-define bars of popup from the "Name" field in database 72 SCAN 73 DEFINE BAR bar cnt OF pop1 PROMPT TRIM(name) + IIF(selected, CHR(17), "") 74 bar cnt = bar cnt + 1 75 ENDSCAN 76 77 DEACTIVATE POPUP 78 79 RETURN 9 Support That Never Sleeps Support That Never Sleeps Introducing the newest member of our software support team: Auto-Tate for dBASE IV. Auto-Tate is an automated audio support system, that is accessed via a touch tone telephone, capable of handling up to 16 callers simultaneously. It is a system designed to "remember" all of the choices each caller makes and route them to the information that pertains specifically to them. It will also allow callers to "save" their place so they can hang up and call back, even days later, and pick up right where they left off. Best of all, this great new service is toll-free! In this "Age of Information", people have a high level of expectation when it comes to response time, availability, and content of the information they seek. Ashton-Tate's software support department is always looking for new ways to meet these expectations. Employing automated voice technology allows us to offer our customers many new advantages, the most obvious being that it's toll free, 24 hours a day, 7 days a week. This means you can get help with problems at night and on weekends but, perhaps, more importantly, you can spend time learning from Auto-Tate at your convenience, which is not always during your busy work day. Beyond convenience, Auto-Tate provides a resource that can be modified and updated continually, it can grow and change in a way that no manual or written word can. Imagine going back to using a typewriter instead of the word processor you now have; if one change is required, it means having to re-type the entire page! Manuals have the same limitation and therefore must be written in less specific terms in order to retain validity over a longer period of time. With Auto-Tate we can address the areas that seem the most confusing to our customers and be quite specific, getting right to the point. Our technicians continually relay what topics concern our customers the very most. Currently, two very asked-about subjects are installation and printing. With Auto-Tate, you can approach these topics from two angles: by listening to tutorial information or troubleshooting a specific problem. You can do a "walk through" for background and preparation, or you can select an error message or symptom you have enountered, and listen to the probable cause and solution. For example: you are ready to install version 1.1, and you have a lot of questions. A walk-thru will tell you what to expect at each step and what you need to do (or not do as the case me be). But suppose you've already installed and done everything correctly, only the program won't start. Troubleshooting will take you through the same steps a technician would, telling you where to look for the problem and giving suggestions or alternatives. The tutorials and troubleshooting are designed by technicians with years of phone support experience; they know the questions customers really ask, and they know how to break down the answers into logical steps. Each subject is handled by a technician who has specialized in that particular area, giving you the benefit of many technicians' knowledge at once. There's a feeling that some customers get when they know the answer is somewhere in their manuals, but they're pressed for time and just need someone to direct them to the right spot. Both tutorial and troubleshooting will refer you to specific pages in the documentation where you can get more information. How To Use It Auto-Tate consists of two parts: a voice tree and a diagnostic. The voice tree is something you are most likely familiar with these days if you call any type of service organization. You select a topic from a menu and follow directions to obtain more information. You can return to the menu and make a different choice if you wish at any step. The diagnostic is quite different, because it is leading you through a progression of informative steps based on the responses you make. Sometimes you will select from a menu of choices, other times you will simply answer "yes" or "no". If you feel you have made an incorrect response or need to repeat something, you press the * (asterisk) to backup one step. In a diagnostic session, you will often be asked to check or try something, which may take time, so you can "save" your session by pressing "00". Auto-Tate will assign you a session number, then you can hang up and go do what you need to do. Call back any time and resume the session by entering your session number when prompted. Suppose you're having printing problems. When you attempt to print, nothing comes out of the printer. Auto-Tate will ask if you can print from a DOS level to determine if you're having a problem within dBASE IV specifically or with your hardware environment. Auto-Tate will tell you exactly what to type on your computer to test the printing capability of your computer from a DOS level. Sometimes, you may not be in a position to try the suggestions at the time of your call to Auto-Tate. Should that be the case, press 00 to save your place in the session, hang up and try the suggestions when you're in front of your computer! If the results are not conclusive and you need more information, when you call Auto-Tate again, you'll pick up right where you left off! Once you're in diagnostic mode, you can save your session anytime, even if you just need to take another call, go to a meeting, or if you're just not in the mood anymore. Auto-Tate will be there when you come back, saving your place so you don't have to remember where you were or listen to the same recorded prompts repeatedly. The following diagram illustrates the menu access for Auto-Tate. As you can see, only 1 and 2 take you to diagnostics, the other selections behave in regular voice tree manner, meaning you can return to the menu and choose something else. Choosing the Different Options The present offerings are exclusively for dBASE IV 1.1. Choosing the first option begins a diagnostic session for tutorial or troubleshooting dBASE IV version 1.1. Choosing option 2, "Resume Previous Session", is for those users who have previously been in a diagnostic session and have saved their place they now wish to resume. Auto-Tate prompts you to enter your session number it assigned during your last phone call on the subject. The third option is "Hardware Certification List". Aston-Tate has certified various PCs, printers and Local Area Networks for use with dBASE IV version 1.1. Here, you can find out if your equipment has been tested and is approved for use with our software. Option #4, "Bulletin Board System" gives phone numbers and brief instructions for accessing and downloading files on the Ashton-Tate Bulletin Board System and CompuServe. Public domain or shareware programs such as PKZIP are explained since you may need one or more of these utilities to use the files you have downloaded. The option entitled "This week's Top Five" is intended to give a quick look at the top five support topics of the week. Included could be announcements, helpful hints, common problems (and their solutions); anything Software Support thinks could be of interest to dBASE IV users and developers. This information will change weekly, so you can check routinely. We always include the option to bypass the introduction and instructions, for those repeat users who are familiar with the system but we do recommend you listen to them the first time around. You'll want to become familiar with how to navigate through the system. In all categories, the information will be updated on a regular basis, evaluating what customers are calling about and adding or updating information. So, you have a dynamic, efficient and constantly available new medium for obtaining support on dBASE IV. We've taken great strides to create this system and have much in store for this service in the future. We feel it will be a tremendous help to our customers getting up and running with dBASE IV. Auto-Tate is going to make a great contribution to our support team. In a sense, we've hired the ideal technician: efficient, listens well, never gets tired or bored and remembers everything. But most importantly to our support staff, he never hogs the pizza!