1 Read Me First This article is reprinted from a recent edition of TechNotes. 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 WHOOPS! Gilbert Catipon Whoops! Gilbert Catipon Protection from the Hazards of Accidental Erasure. Eventually everyone erases a file unintentionally and, oh the frustration it can cause and it's probably happened to you. At that point, you feel like you've been driving without insurance and have caused insurmountable damage. It would be helpful to have some way of protecting us from our impulsive selves. After all, this is the age of protection. We need a tool to make our files a little more secure. While you could buy a "C" library to acquire a .BIN file capable of making a file read-only and hidden, that would be going a bit overboard when all you really want is to keep us or someone else from looking at or destroying your files. Well in fact, just a few lines of C code, and a quick UDF, and maybe you can save the money to buy some frivilous screen saver software instead. Here is the C code: /* ATTRIB.C -> ATTRIB.BIN to call _chmod() */ #include "..\include\dos.h" #include "..\include\io.h" int _argc = 0; char **_argv = (char **) 0L; int errno = 0; void far main(void) { _DS = _CS; _argc = _CX; _argv = MK_FP(_ES,_DI); if(_argc == 2) if(*_argv[0] == ' ') /* GET file attributes */ *_argv[0] = (unsigned char) _chmod(_argv[1],0,0); else /* SET file attributes */ *_argv[1] = (unsigned char) _chmod(_argv[0],1,(int) *_argv[1]); errno = 0; } /* End of file ATTRIB.C */ The .BIN itself is quite straightforward. All it does is call the _chmod function in Borland TurboC 2.0. If you have a different C compiler, your directives may vary. The only tricky part is passing the necessary parameters back and forth. This method of making a .BIN file was discussed extensively in the Febuary '91 issue of Technotes/dBASE IV, but if you can't find that issue then here are the compiler directives you need to turn Attrib.C to Attrib.BIN. tcc -G -O -a -d -Z -c -mc -zPDGROUP attrib tlink /i /s /m attrib,attrib,,\turboc\lib\cc exe2bin attrib How do you use Attrib.BIN? Once we've loaded the .BIN to memory we can call the .BIN directly by passing it a filename and an attribute, such as in the command CALL ATTRIB WITH "CUSTOMER.DBF",CHR(2) We send a CHR(2) because the function _chmod() will use 2 as a parameter to set the file attributes of Customer.DBF to Hidden. The macros that are passed as parameters to _chmod() are in the file #include . If there is an error, the .BIN returns a CHR(255) (a space) otherwise it returns the value we sent it. To use the CHMOD.PRG, enter the following at the dot prompt: ? chmod("customer.dbf","P") This will make CUSTOMER.DBF a read-only AND hidden file and return the letter "P". Note that if the file does not exist, then the UDF returns the string "Customer.dbf?". To get the file attributes, enter the command ? chmod("customer.dbf","?") and you get a "P" again. To reset the file attributes, enter the command: ? chmod("customer.dbf","U"). The syntax for this UDF is CHMOD("","") The possible attributes are: readonly "R" read-only and hidden "P" hidden "H" read-only and hidden and system "X" system "S" no attributes (reset) "U" archive "A" get file attributes "?" Function: ChMod() FUNCTION ChMod PARAMETER fname, attrib attrib = AT(UPPER(attrib), "URHPSvvXlvvvvvvvdvvvvvvvvvvvvvvvA") IF attrib = 0 && GET file attributes attrib = SPACE(1) CALL ATTRIB WITH attrib, fname ELSE && SET file attributes attrib = CHR(attrib - 1) CALL ATTRIB WITH fname, attrib ENDIF RETURN IIF(ASC(Attrib) > 32, fname + [?], ; SUBSTR("URHPSvvXLvvvvvvvDvvvvvvvvvvvvvvvA", ASC(attrib) + 1, 1)) 3 NOTEIT.PRG NoteIt.PRG * Program ......: NoteIt.PRG * Notes ........: Miscellaneous Notes/Telephone book * Syntax .......: DO Noteit ************************************************************************ *Ä Immediately suppress screen activity. * For other settings see PROCEDURE: Envset SET TALK OFF *Ä Initialize variables. gc_currdir = SET("DIRECTORY") lc_deleted = .F. *Ä Open database. USE NoteIt ORDER mainline IF RECCOUNT() = 0 ? "NoteIt cannot run without at least one record in Noteit.DBF" RETURN ENDIF *Ä Set up the environment. DO Envset DO NoteList *Ä Delete any marked records. IF lc_deleted DEFINE WINDOW chkit FROM 7,15 TO 9,65 DOUBLE COLOR w+/n,w+/n,r/n ACTIVATE WINDOW chkit @ 0,5 SAY "Checking for/Removing Deleted Records" COLOR w+*/n PACK RELEASE WINDOW chkit ENDIF *Ä Reset the environment. SET DIRECTORY TO &gc_currdir DO ResetEnv RETURN * EOP: NoteIt.PRG ************************************************************************ PROCEDURE Envset * Notes ........: Setup the dBASE environment *Ä Store environment setttings. sys_bell = SET("BELL") sys_dbtrap = SET("DBTRAP") sys_clock = SET("CLOCK") sys_cursor = SET("CURSOR") sys_delete = SET("DELETED") sys_dev = SET("DEVELOPMENT") sys_escape = SET("ESCAPE") sys_exact = SET("EXACT") sys_path = SET("PATH") sys_safety = SET("SAFETY") sys_score = SET("SCOREBOARD") sys_status = SET("STATUS") sys_talk = SET("TALK") sys_type = SET("TYPEAHEAD") *Ä Set up environment. SET BELL ON SET BELL TO 330,2 SET DBTRAP OFF SET CLOCK OFF SET CURSOR OFF SET DELETED OFF SET DEVELOPMENT OFF SET ESCAPE ON SET EXACT OFF SET PATH TO C:\NOTEIT SET SAFETY ON SET SCOREBOARD OFF SET STATUS OFF SET TYPEAHEAD TO 100 *Ä Set up the colors. sys_norm = COLOR("NORMAL") SET COLOR OF NORMAL TO w+/n *Ä Save the settings to file, then release the memory variables. SET SAFETY OFF SAVE ALL LIKE sys_* TO sysset SET SAFETY ON RELEASE ALL LIKE sys_* RETURN * EOP: EnvSet.PRG ************************************************************************ PROCEDURE ResetEnv * Notes .....: Reset the user's environment. *Ä Restore the original system settings from memory. RESTORE FROM sysset ADDITIVE *Ä Reset Environment SET BELL &sys_bell SET DBTRAP &sys_dbtrap SET CLOCK &sys_clock SET CURSOR &sys_cursor SET DELETED &sys_delete SET DEVELOPMENT &sys_dev SET ESCAPE &sys_escape SET EXACT &sys_exact SET MESSAGE TO SET PATH TO &sys_path SET SAFETY &sys_safety SET SCOREBOARD &sys_score SET STATUS &sys_status SET TALK &sys_talk SET TYPEAHEAD TO sys_type SET COLOR OF NORMAL TO &sys_norm CLEAR ON ERROR ON KEY *Ä Erase the system environment memory file. lc_temp = gc_currdir + IIF(TRIM(RIGHT(gc_currdir, 1)) = "\",; "SysSet.MEM", "\SysSet.MEM") ERASE &lc_temp.. *Ä Close all databases. CLOSE DATABASES RETURN * EOP: ResetEnv.PRG ************************************************************************ PROCEDURE NoteList * Notes ........: View/Edit an entry. CLEAR *Ä Define the varibles. lc_disp = SET("DISPLAY") ln_line = 3 ln_maxline = IIF(AT("43", SET("DISPLAY")) > 0, 39, 20) ll_adv = .T. *Ä Define the keys. ON KEY LABEL Alt-A DO AddEntry ON KEY LABEL Alt-C DO ComSrch ON KEY LABEL Alt-D DO DelEntry ON KEY LABEL Alt-E DO IndxEdit ON KEY LABEL Alt-M DO MainSrch ON KEY LABEL Alt-Q DO IndxSrch ON KEY LABEL F1 DO MyHelp *Ä Define the windows. DEFINE WINDOW viewit FROM 2,35 TO ln_maxline + 1, 76 COLOR n/g,w+/g,w+/b DEFINE WINDOW findit FROM 7, 5 TO ln_maxline - 9, 75 DOUBLE DEFINE WINDOW myhelp FROM 1, 5 TO 23, 75 DOUBLE COLOR w+/g,,w+/b *Ä Draw the screen. @ 2, 2 TO ln_maxline + 1, 33 COLOR n/gr @ 3, 3 FILL TO ln_maxline, 32 COLOR n/gr @ 1, 8 SAY "Index Line" @ 1,50 SAY "Comments" *Ä Get the records. GO TOP ln_recno = RECNO() DO ReDraw GO ln_recno ln_line = 3 DO Litebar DO WHILE LASTKEY() # 27 @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press to Edit comments" READ DO CASE CASE READKEY() = 15 .OR. READKEY() = 271 && @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press then to save changes, then to abort" SET CURSOR on KEYBOARD CHR(29) CLEAR READ SET CURSOR off CASE READKEY() = 5 .OR. READKEY() = 261 && @ ln_line, 3 SAY mainline COLOR w+/gr+ SKIP IF EOF() SKIP -1 ll_adv = .F. ENDIF ln_recno = RECNO() IF ln_line = 20 SKIP -17 DO ReDraw GO ln_recno ll_adv = .F. ENDIF IF ll_adv ln_line = IIF(ln_line = ln_maxline, 3, ln_line + 1) ELSE ll_adv = .T. ENDIF CASE READKEY() = 4 .OR. READKEY() = 260 && @ ln_line, 3 SAY mainline COLOR w+/gr+ SKIP -1 IF BOF() ll_adv = .F. ENDIF ln_recno = RECNO() IF ln_line = 3 DO ReDraw GO ln_recno ENDIF IF ll_adv ln_line = IIF(ln_line = 3, ln_line, ln_line - 1) ELSE ll_adv = .T. ENDIF CASE READKEY() = 34 .OR. READKEY() = 290 && @ ln_line, 3 SAY mainline COLOR w+/gr+ GO TOP ln_line = 3 DO ReDraw GO TOP CASE READKEY() = 35 .OR. READKEY() = 291 && @ ln_line, 3 SAY mainline COLOR w+/gr+ GO BOTTOM SKIP -17 ln_recno = RECNO() ln_line = 20 DO ReDraw CASE READKEY() = 6 .OR. READKEY() = 262 && @ ln_line, 3 SAY mainline COLOR w+/gr+ SKIP -17 ln_line = 3 ln_recno = RECNO() DO ReDraw GO ln_recno CASE READKEY() = 7 .OR. READKEY() = 263 && @ ln_line, 3 SAY mainline COLOR w+/gr+ SKIP 17 ln_line = 3 IF EOF() SKIP -1 ENDIF ln_recno = RECNO() DO ReDraw GO ln_recno CASE READKEY() = 12 && EXIT ENDCASE IF LASTKEY() = 27 .OR. LASTKEY() = 13 KEYBOARD CHR(19) ENDIF IF READKEY() # 15 .OR. READKEY() # 271 DO LiteBar ELSE KEYBOARD CHR(19) ENDIF ENDDO *Ä Release key assignments. ON KEY RETURN * EOP: NoteList.PRG ************************************************************************ PROCEDURE ReDraw * Notes ........: Draw the entries on the screen. PRIVATE ln_line ln_line = 3 DO WHILE ln_line <= ln_maxline .AND. .NOT. EOF() *Ä If the record is marked for deletion, put an * next to it. IF DELETED() @ ln_line, 1 SAY "*" COLOR w+*/n ELSE @ ln_line, 1 SAY " " COLOR n/n ENDIF @ ln_line,3 SAY mainline COLOR w+/gr+ ln_line = ln_line + 1 SKIP IF EOF() SKIP - 1 DO WHILE ln_line <= ln_maxline @ ln_line, 1 SAY " " COLOR n/n @ ln_line, 3 SAY SPACE(30) COLOR w+/gr+ ln_line = ln_line + 1 ENDDO ENDIF ENDDO RETURN * EOP: ReDraw.PRG ************************************************************************ PROCEDURE LiteBar * Notes ........: Draw the LiteBar @ ln_line, 3 SAY mainline COLOR n/g RETURN * EOP: LiteBar.PRG ************************************************************************ PROCEDURE AddEntry * Notes ........: Add an entry lc_addme = SPACE(30) ACTIVATE SCREEN @ ln_line, 3 SAY mainline COLOR w+/gr+ @ ln_maxline + 3, 3 GET lc_addme MESSAGE; "Enter the new entry, or press to abort"; COLOR ,w+/r SET CURSOR on READ SET CURSOR off IF READKEY() < 256 && All numbers < 256 are non-updated codes. *Ä Abort the add. @ ln_maxline + 3, 3 CLEAR TO ln_maxline + 3, 75 DO LiteBar ELSE *Ä Add the record. APPEND BLANK REPLACE mainline WITH lc_addme @ ln_maxline + 3, 3 SAY "NEW ENTRY: " + mainline COLOR g/n @ 4, 5 GET comments OPEN WINDOW viewit MESSAGE; "Press then to save changes, then to abort" KEYBOARD CHR(29) CLEAR SET CURSOR on READ SET CURSOR off @ ln_maxline + 3, 3 CLEAR TO ln_maxline + 3, 75 GO TOP DO ReDraw GO TOP ln_line = 3 DO LiteBar @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press then to save changes, then to abort" KEYBOARD CHR(24) CLEAR READ ENDIF RETURN * EOP: AddEntry.PRG ************************************************************************ PROCEDURE DelEntry * Notes ......: Delete or undelete an entry. *Ä Display an asterisk if the record is marked for deletion, or clear the mark. IF DELETED() RECALL @ ln_line, 1 SAY " " COLOR n/n ELSE DELETE lc_deleted = .T. @ ln_line, 1 SAY "*" COLOR w+*/n ENDIF RETURN * EOP: DelEntry.PRG ************************************************************************ PROCEDURE IndxEdit * Notes ........: Edit the index line. ACTIVATE SCREEN @ ln_line, 3 GET mainline MESSAGE "Edit the Index line ( aborts)"; COLOR ,w+/bg+ SET CURSOR on READ SET CURSOR off IF READKEY() >= 256 && Field was changed ln_recno = RECNO() DO ReDraw GO ln_recno ln_line = 3 ENDIF DO LiteBar @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press to Edit comments" KEYBOARD CHR(24) CLEAR READ RETURN * EOP: IndxEdit.PRG ************************************************************************ PROCEDURE IndxSrch * Notes .....: Search for an INDEXED entry in Mainline. *Ä Initialize variables. lc_srch = SPACE(30) ACTIVATE WINDOW findit CLEAR @ 0,19 SAY "Search for (Quick Search)" @ 1,17 GET lc_srch MESSAGE; "Enter the value to search for ( aborts)" SET CURSOR on READ SET CURSOR off IF LEN(TRIM(lc_srch)) = 0 DEACTIVATE WINDOW findit RETURN ENDIF ln_recno = RECNO() SEEK UPPER(TRIM(lc_srch)) IF FOUND() DEACTIVATE WINDOW findit ln_recno = RECNO() ln_line = 3 DO ReDraw GO ln_recno Do LiteBar @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press to Edit comments" KEYBOARD CHR(24) CLEAR READ ELSE ?? CHR(7) + CHR(7) CLEAR @ 1,15 SAY "Entry Not Found, Press any key" COLOR w+* READ DEACTIVATE WINDOW findit GO ln_recno ENDIF RETURN * EOP: IndxSrch.PRG ************************************************************************ PROCEDURE ComSrch * Notes ........: Search for an entry in comments. *Ä Initialize variables. lc_srch = SPACE(60) ACTIVATE WINDOW findit CLEAR @ 0,17 SAY "Search for (Comments Search)" @ 1, 2 GET lc_srch MESSAGE; "Enter the value to search the comments for ( aborts)" SET CURSOR on READ SET CURSOR off IF LEN(TRIM(lc_srch)) = 0 DEACTIVATE WINDOW findit RETURN ENDIF ln_recno = RECNO() LOCATE FOR AT(TRIM(lc_srch), comments) > 0 IF FOUND() DEACTIVATE WINDOW findit ln_recno = RECNO() ln_line = 3 DO ReDraw GO ln_recno Do LiteBar DO Again @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press to Edit comments" KEYBOARD CHR(24) CLEAR READ ELSE ?? CHR(7) + CHR(7) CLEAR @ 1,15 SAY "Entry Not Found, Press any key" COLOR w+* READ DEACTIVATE WINDOW findit GO ln_recno ENDIF RETURN * EOP: ComSrch.PRG ************************************************************************ PROCEDURE MainSrch * Notes ........: Content search for an entry in Mainline. *Ä Initialize variables. lc_srch = SPACE(30) ACTIVATE WINDOW findit CLEAR @ 0,17 SAY "Search for (Index Line Search)" @ 1,17 GET lc_srch MESSAGE; "Enter the value to search the index line for ( aborts)" SET CURSOR on READ SET CURSOR off IF LEN(TRIM(lc_srch)) = 0 DEACTIVATE WINDOW findit RETURN ENDIF ln_recno = RECNO() LOCATE FOR AT(TRIM(lc_srch), mainline) > 0 IF FOUND() DEACTIVATE WINDOW findit ln_recno = RECNO() ln_line = 3 DO ReDraw GO ln_recno DO LiteBar DO Again @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press to Edit comments" KEYBOARD CHR(24) CLEAR READ ELSE ?? CHR(7) + CHR(7) CLEAR @ 1,15 SAY "Entry Not Found, Press any key" COLOR w+* READ DEACTIVATE WINDOW findit GO ln_recno ENDIF RETURN * EOP: MainSrch.PRG ************************************************************************ PROCEDURE Again * Notes ........: Continue a search (ComSrch and MainSrch only). *Ä Disable the ON KEYs ln_type = SET("TYPEAHEAD") SET TYPEAHEAD TO 0 lc_again = "Yes" DO WHILE .T. @ ln_maxline + 3, 5 SAY "Search Again? " GET lc_again PICTURE "@M Yes,No"; MESSAGE "Press to toggle choice"; COLOR w+/n,w+/g READ @ ln_maxline + 3, 0 CLEAR TO ln_maxline + 3, 70 IF lc_again = "Yes" CONTINUE IF FOUND() ln_recno = RECNO() ln_line = 3 DO ReDraw GO ln_recno Do LiteBar @ 4,5 GET comments OPEN WINDOW viewit MESSAGE; "Press to Edit comments" CLEAR GETS ELSE ?? CHR(7) + CHR(7) @ ln_maxline + 3, 0 CLEAR TO ln_maxline + 4, 77 @ ln_maxline + 3, 20 SAY "Entry Not Found, Press any key" COLOR w+* READ @ ln_maxline + 3, 0 CLEAR TO ln_maxline + 3, 70 GO ln_recno EXIT ENDIF ELSE CLEAR GETS EXIT ENDIF ENDDO SET TYPEAHEAD TO ln_type RETURN * EOP: Again.PRG ************************************************************************ PROCEDURE MyHelp * Notes ........: Help system. *Ä If you want to add your own help pages, simply add another procedure * called Page (where is the next number), and increase ln_maxpg. ln_page = 1 ln_maxpg = 3 lc_temp = " " ACTIVATE WINDOW myhelp DO WHILE .T. CLEAR lc_page = "Page" + LTRIM(STR(ln_page)) DO &lc_page @ 0, 0 GET lc_temp MESSAGE; "PgUp - previous screen, PgDn - Next Screen, ESC - Exit"; COLOR ,g/g READ IF READKEY() = 12 EXIT ENDIF ln_page = IIF(READKEY() = 7 .OR. READKEY() = 263,; IIF(ln_page = ln_maxpg, ln_page, ln_page + 1),; IIF(READKEY() = 6 .OR. READKEY() = 262,; IIF(ln_page = 1, ln_page, ln_page - 1), ln_page)) ENDDO DEACTIVATE WINDOW myhelp SET MESSAGE TO RETURN ****************************************************** PROCEDURE Page1 *Ä Help Page #1 TEXT Available keys: - Exit. - Edit comments. - Down one record. - Up one record. - Down one page. - Up one page. - Bottom of File. - Top of File. ENDTEXT @ 20, 68 SAY CHR(25) COLOR w+*/r RETURN * EOP: Page1 **************************************************************** PROCEDURE Page2 TEXT Available keys (cont'd): - Add and entry. - Search the comments field - Mark/Unmark an entry for deletion. (This will put a blinking "*" next to the record that is marked for deletion. The record will be physically deleted when you quit the program. If you press while on a record that has a blinking "*", the record will be unmarked for deletion. - Edit the Index line - Search the contents of the Index line. ENDTEXT @ 0, 68 SAY CHR(24) COLOR w+*/r @ 20, 68 SAY CHR(25) COLOR w+*/r RETURN * EOP: Page2 ********************************************************************* PROCEDURE Page3 TEXT Available keys (cont'd): - Do an index search of the index line. This search is the quickest search, however it will search the beginning of the Index line. For example, if you had a line: Doe, John You could not enter "John" to find it, you would have to enter characters starting from the beginning of the Index line (i.e.: "Do". To find "John", you would need to use . ENDTEXT @ 0, 68 SAY CHR(24) COLOR w+*/r RETURN * EOP: Page3 **************************************************************************** FUNCTION Color * Format: * Color( ) * = NORMAL, HIGHLIGHT, MESSAGES, TITLES, BOX, INFORMATION, FIELDS * or a variable with all colors stored in it. * Ver: dBASE 1.1 * * The Color() function either returns or sets colors returned with the * SET("attribute") setting. * If is a color string then null is returned otherwise the color * setting is returned for one of dBASE IV's color options. * * See Also: SET("attribute") * * PARAMETERS set_color PRIVATE color_num, color_str, cnt set_color = UPPER(set_color) IF set_color = "COLOR" *- Return standard, enhanced, border colors only RETURN SUBSTR(SET("attr"),1, AT(" &", SET("attr"))) ENDIF *- Declare array to parse color options from SET("attr") PRIVATE color_ DECLARE color_[8] *- Determine if user is restoring colors vs. saving colors IF " &" $ set_color color_str = ","+set_color+"," && Restore color attributes ELSE color_str = ","+SET("ATTRIBUTE")+"," && Save color attributes ENDIF *Ä Stuff array with individual color setting color_str = STUFF(color_str, AT(" &", color_str), 4, ",") cnt = 1 DO WHILE cnt <= 8 color_str = SUBSTR(color_str, AT(",", color_str ) +1 ) color_[cnt] = SUBSTR(color_str, 1, AT(",", color_str ) - 1) cnt = cnt + 1 ENDDO IF " &" $ set_color *Ä Set color back. SET COLOR TO ,,&color_[3]. && Border color. SET COLOR OF NORMAL TO &color_[1]. SET COLOR OF HIGHLIGHT TO &color_[2]. SET COLOR OF MESSAGES TO &color_[4]. SET COLOR OF TITLES TO &color_[5]. SET COLOR OF BOX TO &color_[6]. SET COLOR OF INFORMATION TO &color_[7]. SET COLOR OF FIELDS TO &color_[8]. ELSE *Ä Return color string requested. DO CASE CASE set_color $ "NORMAL" color_num = 1 CASE set_color $ "HIGHLIGHT" color_num = 2 CASE set_color $ "BORDER" color_num = 3 CASE set_color $ "MESSAGES" color_num = 4 CASE set_color $ "TITLES" color_num = 5 CASE set_color $ "BOX" color_num = 6 CASE set_color $ "INFORMATION" color_num = 7 CASE set_color $ "FIELDS" color_num = 8 ENDCASE ENDIF RETURN IIF(" &" $ set_color, "", color_[color_num]) * EOP: Color 4 The Era of Protection Joe Stolz The Era of Protection Joe Stolz Perhaps you don't want to think about it but someone could be looking over your data files while you are out of the office! Perhaps you don't have any data that is truly confidential but it's not hard to imagine the sort of information that is best not seen by others. If you are a manager, or you work in Payroll, you will need to prevent others from seeing the private information of other individuals. After all, if Mr. Prehensile were to discover that Miss Halcyon was making more than he, it could cause a major downswing in office rapport. Perhaps you want to restrict others from entering dBASE IV altogether. That way, all of your data files will be secure. What if you work on a Local Area Network installation and you share files with others? Everyone must access the same file but you alone should see or modify certain fields in the file structure. Does this sound like a task too big for dBASE IV? It really is simple when you use Protect! Protect is not a new feature of dBASE IV. It's been around since dBASE III PLUS. However, in dBASE III PLUS, you had to have a multi-user installation (even if it was on your single-user machine) to get Protect to work. Further, you had to exit to DOS to run the Protect program. It was a separate utility. Now Protect is one of the bevy of utilities available through the Tools menu in the Control Center. This new, easier access to Protect makes it a good topic for review in the context of dBASE IV. There are also some new features that were added to make Protect easier to use. How It Works The dBASE IV Protect system is based on login names and passwords like most computer security systems. The dBASE IV login screen is visible only after you have set up a Protect system. Once you have it set up, every time you enter dBASE IV you will first see a login screen requesting a User name, a Password and a Group name. The User name and Password are used to restrict access to dBASE IV to only those that have a user profile within Protect. A Group name is extremely important as it serves as a way to designate a group of database files that require access by a particular subset of users. If you are not part of the group, you will not be able to open the file. Database files and their contained data, including their memo field contents, are physically encrypted through Protect. Once encrypted, the entire file is unreadable through DOS or other utility programs, except through dBASE IV as one of the users of the Group that has been designated to have access to it! Protect offers a third level of security too. You can assign priority levels to your users and restrict access to specific fields within the database structure. This restricted access can span the range from full reading and edit capability of all fields in the file, to the ability to see only the data in the field but not to change its content, to the ability to make a field appear as if it didn't even exist in the file structure! All this is possible through Protect. When you run Protect it creates a file called DBSYSTEM.DB. If you are on a single user system, you find this file in the dBASE IV directory. If you are on a LAN system, the file is created in the DBNETCTL.300 directory which also contains the login access files. This file contains the user profiles and is itself encrypted using the Protect system administrator I.D. You will want a copy of this file available in case it becomes inadvertently deleted. A new feature within Protect is the Reports menu which can produce a hard copy listing of your users and their passwords, allowing for full documentation of your Protect system. Assigning User Groups As the manual goes into some detail explaining the features of Protect, it would be more useful to concentrate on some aspects of Protect that are particularly confusing to some people. The first thing that you will end up doing in Protect is creating user profiles. Each user must be assigned to a group (assigning names and passwords is pretty straight forward). As noted in Using the Menu System, pages 14-23, a user can belong to several groups but a file can be encrypted for only one group to access. If a user needs to access files from another group of which he is a member, he must login again and therefore maintain two full user profiles. There is nothing wrong with having the same name and password for a particular user, and to have two different group names assigned for each of the two profiles. The uniqueness of each user profile is in the combination of the three required key fields: Name, Password and Group name. The fact that a file can be encrypted for only one group is crucial to your understanding of how dBASE IV limits access to encrypted (protected) files. If you have set up a file to be accessed by one set of users, a new user needing to access that file must belong to the same group. The group name is the key to a user being able to access the contents of a file. To each member of the correct group, the file seems as if it contains plain English names and data. Anyone outside of the group who attempts to access an encrypted file will be stopped by the message, "File is encrypted" when they try to USE the file. Attempting to view the file from DOS will lead to the same frustration. Whereas the initial entry to dBASE IV is blocked by the need for a user profile, even if you find a way to circumvent the login, this second level of protection hides your data. That is to say that even properly logged-in users can be prevented from seeing data in a file that is encrypted for a different group. User and File Access Levels The next issue is one of user access levels. In dBASE IV, a level of 1 is the most powerful level. This translates to level one being the least restrictive level. Level 8 is the most restrictive level. The absolute values 1 through 8 have no real intrinsic meaning in and of themselves. There may be no practical difference in your system between level 7 and 8. Protect merely offers 8 levels of security differences for systems whose security levels must be that complex. It is up to the Protect system administrator to establish the assignments of security equivalence. It is then where the eight levels can take on a more precise security level based upon what levels are higher or lower than any particular level. Initially, all users are assigned a default level of 1 (highest access) and all file privileges are assigned a level of 8 (least restrictive). A level 1 user can perform all functions: read, update, extend and delete since the highest level of restriction that can be assigned to each of these operations is one. A user of level five, however, can perform operations assigned to level five through 8 but not those assigned to level four down to one. Reviewing the file privileges again, they are, in order of increasing importance, Read, Update, Extend and Delete. To give a simple demonstration of the increasing power of these levels, let's say that Read allows you to read the data only. No changes can be made to data contained in the file. The Update privilege allows changes to be made to existing data. Extend privileges provides for the addition of new data in the form of new records added to the file. The Delete privilege means the you can remove records from the file. This increasingly important set of operations is what requires careful assignment to the various levels of users in your database management system. Read access is the ability to simply see data in a file. This is the lowest level of all file access privileges. You should realize that if you have a user who must be prevented from even looking at the contents of a file, that user really should not be included in the group. If a user is part of the group that accesses a file, the minimum ability he will usually have is to see the contents of the file, though he may be prevented from altering the record contents. Nonetheless, if you assign read access to level 7, your level 8 users will be unable to do anything to the file. In a different vein, assigning Read access to a number smaller (more restrictive) than the level assigned to Update essentially nullifies the read-only capability of your Read assignment and causes Update and Read to be granted at the same level. This is okay if you understand what you are doing, but for the sake of simplicity, you should assign the four levels of file privileges in an increasing order to an ever decreasing set of values. The next level up is that of Update privileges. This is the ability to change (or edit) the contents of a record. The next level is the ability to Extend (or append to) the file. A common question raised is whether a data entry clerk can have the ability to add new records, but not to change the contents of pre-existing records. Since adding records is really the same as editing a blank record (putting contents into blank fields), the answer is no. However, to attain this kind of restriction, data entry clerks can be given access to special data entry files that will contain only new records and be denied access to the main database, preventing the changing of data in that file. New records they enter can later be appended to the master file. Another, similar, question is whether a data entry clerk can be restricted from viewing data but only allowed to enter new data. This is even more impossible given what was discussed above. Remember, the minimum privilege is to view a file (called read-only access). If a user has a higher access right like Delete, the user would, out of necessity, retain the rights to the lower, more fundamental access privileges. The highest available privilege is to Delete records. If you want to visualize how all this fits together, let's try assigning some numbers to the four privileges. Say that Read is assigned level 6, Update is also assigned level 6, Extend is assigned level 4 and Delete is assigned level 2. A level 8 user will not be able to use the file. The same is true for a user who has a user access level of 7. A user with an access level of 6 will have both Update and Read privileges. In this example, there can be no users that have read-only rights. Data entry must be done by those with access rights of 4 and above. Deletion can be done only by those with levels of 2 or 1. Field Access Levels The rights and restrictions discussed to this point apply on a full file basis. That is, up to now we saw that we could totally restrict a user from changing data in a record, or allow them the ability to add records to a file. dBASE IV also offers the ability to apply an even finer level of restrictions to a file, on a field by field basis. Field privileges are not commonly assigned judging from my discussions with users. I believe that this is due to the fact that the concept of field privileges is three levels removed. Thinking about login restriction, then file access privileges in relation to user access levels, then to field level restrictions tends to boggle the mind. It's a level of abstraction that is too much to deal with. Once again, user access level is the basis of field privileges just as it is with file privileges. Fields can have three levels of access privileges, Full, Read-only, and None. "Full" translates to no restrictions, "Read-only" restricts the user to only viewing the contents of a field. "None" is remarkable in that fields designated as such "don't exist" to users of that access level. It's remarkable since not only can they not see the data, they can't even see the field in the file's structure. DISPLAY STRUCTURE will not display fields designated as None, as if the field didn't even exist. It's a pretty tricky thing, a potential source of problems. Field privileges are assigned for each User access level on a field by field basis. That is, you establish which fields can be modified, viewed only, or not visible at each User level. You might decide that a user of level 8 should not see the Salary field in the Payroll file. However, this same user might be the one who adds the basic personal data to the other fields in the file, having then a mixture of Full and None field privileges in the file. Establishing field privileges this way might seem like an alternate way to set up file level privileges since you can establish which fields are visible and modifiable for all User access levels. Actually, Field access privileges must work in conjunction with File access privileges. Established File privileges will take precedence over Field privileges. These in turn must work in conjunction with User access levels. Let's look again at our example of this concept. The data entry clerk must have Extend ability (capability to APPEND to the file). As a fine adjustment on this ability, you want to block the data entry clerk from seeing the contents of the Salary field. This fine tuning is what the Field access concept is all about. Note that for Human Resource employees who do not make changes to the data at all and who are designated as level 7 or 8 users (such as Read-only), assigning Full field privileges to all fields WILL NOT override the Read-only rights established on the file as a whole. That is, Field privileges do not override File privileges. This should help to alleviate some of the ambiguity surrounding the use of field level restrictions. It is best used as a fine tuning capability over the more "coarse" file level restrictions. Field level access is usually used sparingly, don't try to over use it. Encrypting the File Once the File and Field levels are established and you are done, you must exit from Protect. Of course, you will be Saving all changes made during the Protect session. One of the most common "problems" that new Protect users report is that their new access scheme doesn't work at all. Without exception, this can be attributed to a single cause. When a file is encrypted, it is created as a new file. The original file is left untouched and the newly encrypted file has the same name as the original file but with a .CRP extension. Thus, when you use the .DBF, it is available to any level of user. This is NOT the protected file. This is done for your protection, but it certainly is confusing to the Protect user! This simple fact is documented in Using the Menu System under "Other Considerations" in Protect, on page 14-38. It's not really an "other consideration," it's a crucial consideration if you ask me! At any rate, to use your newly encrypted file you must rename it and give it a .DBF extension. I'd suggest that you rename it to a new parent file name and that you make a backup of both the original database and the .CRP file to boot. Reports of the file access privileges and such can only be made from files that have a .CRP file extension. Another little known and little realized fact is that once a set of privileges is established for a file and you exit Protect, the changes are permanently recorded in the .CRP file and if you desire to modify the access rights established you will have to start over again establishing all rights to an unencrypted parent file! The .CRP file cannot be modified once saved and only .dbf files can be used as the basis of setting up new field privileges. For this reason, it is imperative to print a report of the field and file level rights that were established within Protect for each file. dBASE IV offers a Reports menu for this purpose. The Report menu reads and reports on .CRP files only. If you desire to modify any particular rights in your file it will help to see graphically what rights were previously established for the file. It certainly can save a lot of time and grief in the event of a disaster. Summary The abilities of the dBASE IV PROTECT feature is a complicated area to learn. Protecting your data is crucial in many database systems. Your task is to understand what is going on in the dBASE IV Protect system and to try to get the most out of it. Being forewarned about the interrelations between the various aspects of Protect is your best protection against frustration with the system. Rest assured that it works and can be downright powerful once you get the hang of it. Give it a whirl! 5 Sizing Up an Application Don Powells Sizing Up an Application Don L. Powells What is an application? This is a very broad question. Can you narrow it a bit? From a developer's perspective, what are the components of a dBASE IV application? This is a fair question. Before you go about developing an application it would seem a reasonable prerequisite to know what an application is. Seeing a completed application running as a whole, we sometimes forget that there are discrete parts integrated under the surface, combined to solve a problem or accomplish a task. This article describes the parts or components that make up an application with the intent to draw attention to many of those elements that go into making an application a good one. Setting the Environment Most applications are affected by the environment in which they run. It is very important to set the operating system environment so that your application will run in it. Under DOS, for example, the Config.SYS file must contain a large enough value for the FILES variable to allow you to simultaneously open the maximum number of required files. If your code depends upon the contents of the PATH or other DOS environment variables, they must be set before program execution. Configuring the System The dBASE IV environment must be configured, initially, upon installation of your application. The dBASE IV SET commands can be used to determine settings such as whether the clock will be on or off, displaying 12 or 24 hour format. The SET commands also control screen colors, the date and numeric format (especially important with international applications), the currency format, the status of the bell, and disk drive/directory search paths. In conjunction with the SET commands, global variables are often used to maintain system status information. These variables should be saved to a .MEM file to be restored each time the application is run so that the user does not have to reconfigure the system each time. The SAVE TO command will store all or a portion of variables present in your system. Unless the variables change regularly, it is not necessary to SAVE the variables at the end of each session. The RESTORE command will reset these variables in a new dBASE session and will be necessary at each start-up. Neither of these processes is automatic and must be set up in your start-up and exit routines. To prevent the user from having to worry about what files are necessary for the application to work, it is a good practice to include an initialization routine that checks for the presence of the needed files and creates them if they are not there. These files may include data or index files, .MEM files as discussed above, memo (.DBT) files, and any special files you may have created. The initialization routine opens the files and sets the necessary relations and filters, establishing your desired views of the data. Displaying the User Interface The user interface is the means by which the user communicates with the application and, indirectly, the way the developer communicates with the user. Some of the best rules for user interface design are from the Apple User Interface Guidelines. I have paraphrased these guidelines below. General Principles of User Interface Design ù Use concrete metaphors for computer processes that correspond to the everyday world. A check-writing program, for example, may display an image of a check on the screen as data is being entered. ù Provide sensory feedback in response to the user's physical actions so that they feel they are in charge. Make a noise, make a visible change on the screen, electrically shock the user, do something to let them know that you recognize their actions. ù Allow the user to select from alternatives displayed on the screen. Let them use recognition rather than recall. Most people like multiple choice much better than fill-in-the-blank. ù Be consistent within and across applications to ensure ease-of-learning, and familiarity. ù Make sure that what the user sees on the screen does not differ significantly from the eventual output. ù Let the user initiate and control all actions. Provide warnings for risky activities but allow the action to proceed if confirmed by the user. ù Keep the user informed of the progress of operations with immediate feedback which tells how long delays will last and why. ù Forgive the users when they make mistakes by allowing their actions to be reversible. Let them know when their actions are not reversible. In retrospect, perhaps it's better not to shock the user as was previously recommended. ù Provide a conceptual sense of stability by maintaining a number of familiar "landmarks" on the screen and avoid random changes of environment. ù Provide aesthetic integrity by making different things look different on the screen and giving the user some control over the look of their workplace. A number of elements are used to build the user interface. Some of the major user interface elements are briefly described below. User Interface Elements ù Menus are used to navigate through an application. Pull down menus have become very popular because they allow the user to see what functionality is available, where he is in the program, and the path followed to get to there and back. Popup menus are great for displaying pick-lists from which a single selection is made. Horizontal bar menus prove useful when you need to display a limited number of choices in a small space. Menus can be built using the dBASE IV applications generator. Menus and pick-lists provide that multiple-choice vs. fill-in-the-blank preference previously mentioned. ù Windows allow you to collect and display related information while separating it visually from unrelated items on the screen. Dialog boxes are built from windows and used to ask users questions or to pass along important information. Alert boxes are similar to dialog boxes except they are commonly used to issue warnings. Check boxes are commonly displayed in a window to let the user make multiple simultaneous selections from a list. A table of data from a related file or a body of text from a memo field may be displayed in a window as well. ù Data input forms let the user enter data on one or more full screen pages of data entry blanks. Data input forms can be built using the dBASE IV forms generator. ù Data display forms display the data on one or more full screen pages without the ability to edit or change the data. Data display forms can also be built using the dBASE IV forms generator. ù Progress indicators include odometers, blinking messages, and thermometer-like bars that show the user how far a process has gone and how far it has to go. Without progress indicators the user is left to wonder whether the application has failed and locked up the computer. Progress indicators help prevent inopportune rebooting which can be a source of file loss and corruption. Data Manipulation and Processing Once data has been entered through the data input forms or some other method, your application will process that data toward your desired end. A description of some of the more common data manipulation and processing functions follow. ù Data Verification entails validating the type, format and content of the data that is input. This process may be as simple as a PICTURE clause or VALID expression in an @..GET statement or as complex as a large user defined function. ù Data Storage involves the transference of validated data from memory or a temporary file to a more permanent master .DBF file. If data storage is localized in one place, any special processing required later can be added in one place. ù Data Access functions are used to search for requested data records by using indexed SEEKs, ad hoc LOCATEs, or sequential steps through FILTERed .DBF files. ù Data Retrieval is the opposite of data storage. It involves the transference of data from a .DBF file to memory variables, an array or a temporary file for display or editing. ù Data Deletion allows you to remove unwanted records from your system files. Localizing this process lets you put in safeguards against accidental loss of important data. Some developers use the PACK command but others just blank the record out to make their data storage function more efficient and faster. On large files with many indexes, PACKING may be less desirable than a blanking method. However, the latter method requires additional programming efforts to ignore these blank records in calculations and reports. ù Unique Processing may be required for each application you create. The processing for preventing duplication of data will most probably occur before data storage, during a validation process. However, it is also possible that duplicates are merely omitted from the reporting or calculating process by filtering them out beforehand. ù Data Output could be called reporting but involves outputting the data to not only the printer, but to the screen, a file, or via telecommunications. The output may take the form of a formatted report, labels, invoices or other special forms, or specific file types that other programs can read. Many of these forms of output may be generated in dBASE IV via use of the report or label generators or exporting commands such as COPY and EXPORT. Context-sensitive Help Nearly all commercial applications sold today provide context-sensitive help and your user will probably expect no less from you. As a minimum, the help system should provide the user with general information about your application and aid when using the most difficult portions of the application, however, there may be times when you cannot predict what will be difficult for your users. Therefore, you should make your help system flexible enough to absorb additions, deletions and edits with relative ease. An advanced help system allows the user to add his own help text to what you provide. If the help is going to be substantially long, it is preferable to include help information in a supplemental .DBF file, possibly using memos rather than hard-coding the help into the program itself. Maintaining the System A group of activities fall within the realm of system maintenance. A user may decide that one color scheme is no longer attractive and want to change it. A different user takes over the machine and decides to reconfigure the system totally to their desired state. Printers come and go, replaced by newer models or substituted by inferior ones when they malfunction. A conscientious developer should not make provision for only one or two specific printers. You should also accommodate customization after installation. If the index files get corrupted by a power outage during a write, the user should be able to recreate them from a menu option. Many times, our technicians here will get a call from a dismayed user who can't make a custom application work. Often times, it's only a matter of rebuilding an index file, a function that won't normally take more than a few seconds. But without proper instruction and a means for recreating such files, hours of constructive time can be lost. It makes sense for some applications to allow the user to create a new, empty set of files. If the user wants to practice with dummy data, new files are a great aid for helping them get started with the real data. Only you should also provide for the erasure of these files so that unneeded practice files do not build up in the user's directory. Speaking of size, if you anticipate that the size of the system files will grow to unmanageable sizes filled with historical data, you may want to implement an archive and restore system to allow the user to put unneeded records in an archive file until later required. Archiving may also speed up the processing of the application because there are less records for your application to maintain. Provide a means for your users to backup and restore their data as part of a maintenance menu. This system can be a simple file copy to a user specified drive, or using the DOS commands, or a complete system that splits files across multiple floppies or other media if the file is too large to fit on one. Of all the features of your application, this one is of a critical nature. No insurance for possible data corruption or erasure is an invitation for inevitable disaster. As the architect of a system, you should provide the system for easy and reliable backup and encourage your users to follow through regularly. Handling Errors You should anticipate and prevent the occurrence of non-critical errors. Make sure that only the proper data type can be placed in a data entry field by using formatting and validation. Unfortunately, there are some critical errors that cannot be easily prevented by dBASE IV error trapping (such as backing up to an open disk drive or printing to a printer that is off). In those cases you should try to allow the user to recover or terminate gracefully under your application's control. An electronic error log should be automatically generated by your error handler to help you to debug problems without having to depend upon the user to describe them. It is easier to handle errors if you group them into categories like disk errors, print errors, memory errors, and so on. You can then concentrate on handling just that one category of errors at a time instead of trying to accommodate every possible error in one giant CASE construct. Again, if your system is vast and the error messages numerous, consider placing the errors and their error numbers in a database that can be called by your program when an error occurs. Technical and User Documentation The comments in the source code files contain much of the technical information needed to maintain the application, but often, more documentation than that is needed to maintain an application. It is helpful to have a description of the overall application and how all the pieces fit together. A code analysis utility could prove helpful in creating flow charts, variable usage tables, and more. Other utilities can provide "screen-shots" of different screens that appear in your application. The visual association can be helpful in increasing the understanding of how to operate your application. User documentation is a combination of what is provided on-line and in print. As a minimum, the user should be able to understand how to get started using your application. Ideally, the user would never have to call you with a problem that could not be solved by using the program or its documentation. Then again, where would our technical support department be if that were true. Miscellany Additional functions may be added to an application to implement networking or password protection. Transaction processing may become necessary in a transaction-oriented system like accounting. If your application requires some functionality outside of what dBASE IV can handle, you may need to include modules written in C or Assembly language to handle those tasks. The pages of TechNotes and the Ashton-Tate BBS are excellent sources for such routines. Summary So, what is an application? It is a collection of discrete parts integrated to solve a problem or accomplish a given task. Each part has a purpose and the absence of even one part increases the likelihood that your application will not reliably suit those who use it. The parts that have been discussed here are: ù Setting the Environment ù Configuring the System ù Displaying the User Interface ù Data Manipulation and Processing ù Context-sensitive Help ù Maintaining the System ù Handling Errors ù Miscellany ù Technical and User Documentation When you conscientiously combine these parts and others you find necessary, you get an application that provides the users with a more efficient way of processing data and you with a minimum of headaches (from disgruntled and perplexed users calling for technical support) and a maximum amount of cashflow (from lots of orders and/or a raise). 6 Etc. Etc. Checking For Directory Existence At some point during the course of application building, it often is necessary to verify the existence of critical files needed for proper program operation. This is easily accomplished through use of the FILE() function. A program segment may look something like this: IF .NOT. FILE("MyApp.DBF") . . *Produce error message for missing file "MyApp.DBF" . . ENDIF *Continue normal processing here... Checking for directory (or drive) existence can be accomplished in a similar manner. Ironically, the FILE() function is still the function to use. But since the FILE() function needs a file name we'll have to trick it by using a DOS device name such as NUL, CON, PRN, etc. This is possible since DOS allows you to open a device the same way you open a file! NUL is the preferred device name to use since it's not likely to interfere with an operation the way opening CON or PRN may. The following example shows how to accomplish this... IF .NOT. FILE("\MyAppDir\NUL") && or FILE("B:\NUL") for drive . . *Produce error message for missing directory "MyAppDir" . . ENDIF *Continue normal processing here... Setting Natural Order in the Applications Generator When using the dBASE IV Applications Generator, the Override assigned database or view option allows you to change the active database and its order. However, changing the order of the file to Natural order (no index) once a particular order is in place, is not clearly documented. This can be accomplished by entering a left and right bracket ([]) or two single quotes ('') in the following areas: ù In the Set index order option under the Item menu. ù Under the ORDER option of the Override assigned database or view option of either the Menu or Item menu. This has the effect in the resultant dBASE code of a SET ORDER TO command which resets the file to natural order. Invoking Print Screen from a Program Since the Print Screen or Shift-Print Screen key has a special use on PC-compatible computers, it does not return a value to dBASE IV. This makes it impossible to invoke a print screen through the use of the KEYBOARD command. Fortunately, printing the screen can be accomplished through use of a very small .BIN file which is relatively easy to create. Either of the following two measures will create the file Prntscrn.BIN. At the DOS prompt type COPY CON Prntscrn.BIN and press Return. Then, while holding down the Alt key, enter the numbers 205, 5 and 203 on the numeric keypad, releasing the Alt key after each number. Lastly, press the F6 key and then Return. This will close and save the file. The second method for creating this .BIN file must be invoked from the dot prompt or in a program. The dBASE code follows. SET PRINTER OFF SET PRINTER TO FILE Prntscrn.BIN ??? "{205}{5}{203}" SET PRINTER TO Having created the file, issue the command LOAD Prntscrn. Thereafter, to print the screen, issue the command CALL Prntscrn from your program: Seeing Your True Colors While it's true that there is now a way to use the SET() function to return the values of your current color settings, it doesn't really show you what these settings are or what they look like. It is not hard to devise a small routine that shows the eight different color settings in the actual colors (on a color monitor of course). However, when the colors for foreground and background are the same, such as N/N (or black on black), there may be no indication on screen if your background color is that same color. t * CSHOW.PRG PRIVATE showcolor, cstring, comma, talkwas, cursorwas ON ESCAPE KEYBOARD CHR(0) IF SET("TALK") = "ON" SET TALK OFF talkwas = .T. ELSE talkwas = .F. ENDIF cursorwas = SET("CURSOR") = "ON" SET CURSOR OFF DEFINE WINDOW colorshow FROM 5, 10 TO 16, 46 220, 223, 219, 219, 220, 220, 223, 223 &&ASCII box characters cstring = STUFF(SET("ATTR"), AT(" &" + "& ", SET("ATTR")), 4, ",")+"," ACTIVATE WINDOW colorshow comma = 0 showcolor = "" cntr = 1 *--note final comma in following string pstring = "normal,highlight,border,messages,titles,box,information,fields," DO WHILE cntr < 9 @ cntr, 1 SAY "Color of " +left(pstring, AT(",", pstring) - 1) + " is" DO Parse @ cntr, 26 SAY showcolor COLOR &showcolor cntr = cntr + 1 ENDDO comma = INKEY(0) DEACTIVATE WINDOW colorshow RELEASE WINDOW colorshow IF talkwas SET TALK ON ENDIF IF cursorwas SET CURSOR ON ENDIF ON ESCAPE RETURN PROCEDURE Parse *-- parse attribute string comma = AT(",", cstring) showcolor = LEFT(cstring, comma - 1) cstring = SUBSTR(cstring, comma + 1) *-- parse pstring pstring = SUBSTR(pstring, AT(",", pstring) + 1) RETURN 7 Digital Notepad Mike Dean/George McMullen] The Digital Notepad Mike Dean and George McMullen As we rely more and more on doing various tasks on our computer, several utility-type programs have popped up on the software market. For instance, wouldn't it be convenient to have a utility that would popup a window for you to jot down notes in, record an address, a telephone number or a reminder about some weekly activity? Well I thought so, so I wrote one and called it NoteIt. NoteIt is based on a database with 2 fields: an indexing field and a memo field. Use NoteIt for a address book. Put the name of the individual or company, and then contact names, address, phone numbers and any comments in the memo field. Since it is a memo field, you can enter a great deal of information. You might be wondering how do you retrieve all of these random notes of information? To make locating your recorded thoughts easier, there are three searches in NoteIt: ù Quick Search: This is a SEEK of the index line and is the fastest of all of the searches. This requires that you search the entry starting from the leftmost characters in the index field. For example, to look up the entry "Doe, John", you would have to start your search criteria from the last name ("D", "Do" or "Doe", and so on ...). ù Main Search: This is a search of any data in the index line. For example, using the above information you could type in "John" and it would find "Doe, John" as well as any other records that contained the word or partial "John". ù Comments Search: Like the Main Search but it searches the comments (memo) field. You can search for any word or set of words in the memo field. These last two searches are sequential and will consequently take longer on some machines, depending upon the size of your database. You will need to create the simple database, called NoteIt, with the following structure. Field Name Type Width MainLine Character 30 Comments Memo 10 For the program to function correctly, you must add at least one record prior to using NoteIt. This initial record can be edited while in the NoteIt program by the special editing key Alt-E. Other special editing keys are available on-screen when using NoteIt by pressing F1. You still press Esc to abandon a memo edit or Ctrl-End to save the changes. One point worth noting here is that the cursor is normally positioned on the memo field in a window. Because of this, you will need to press Return to regain access to the popup picklist of names to the left of the memo window. Another point that might raise questions is the presence of the ruler in the memo field. When not active, there is no ruler visible. Upon entering the memo field, in edit mode, the ruler appears, shifting the text down. The question may then be, how do you eliminate the ruler? The only way to do so is through the Words menu (accessible by pressing Alt-W or F10), changing the Hide ruler option to YES. This setting is not for the dBASE session but for that particular editing session. Consequently, the next time you enter a memo for editing, the ruler will once again appear. There is no additional parameter that can be added to the GET command line of a memo that provides for elimination of the ruler. All in all, this program provides a handy utility and the code shows some interesting programming concepts as well. 8 UDF Library UDF Library "At the tone, the time will be..." Although most of you are aware that you can do basic arithmetic functions with date and time fields, it is not as well known that you can add decimal values to a date field and obtain a time differential. A need for this usually comes when trying to determine an elapsed period of time. Functions that accomplish this have been seen in the pages of TechNotes/dBASE IV in the past. Here now is another variation on how you might accomplish it. In the function called Elapsed, the variables sdate and edate are date type variables while stime, etime and result are character type using the template HH:MM:SS. To start the timing sequence, enter x = elapsed(1) at the dot prompt or in a program. To stop or mark the time, x = elapsed(0). The 0 can be passed repetitively to provide for numerous time measurements from a singular starting point. Creating Temporary Files There are times when you might want your own application to be able to create its very own temporary files like dBASE IV occasionally does. The TempFile() UDF below returns the name of a temporary file that can be used in your program. For example, Temp = TempFile() USE TRAVEL COPY TO &Temp USE &Temp REPLACE ALL Cost WITH Cost * .06 AVERAGE Cost TO avg cost ERASE &Temp TempFile() honors the TMP environmental variable, so that the file that your program ends up using is created and processed along with the rest of your dBASE IV temporary files. Be sure that your program erases the temporary files it creates, (as shown in the example above,) or you might end up with a directory filled to the brim with temp files. Fonetic Philes One goal of a developer is to make his software foolproof. If your application has areas of functionality that can dynamically use any .DBF or other file type that the user pleases, then it isn't unreasonable to assume that the user will not always remember the name or spelling of a target file. FileHelp() is a real nifty way to ensure that a spelling error or lapse of memory will not slow the user down. FileHelp() takes two parameters: the filename that the user enters, and a file extension. A list of SOUNDEX() spellings and sound-alikes of filenames with the specified extension pops up. The selected spelling from the picklist is returned unless no similar spellings exist or the user presses Esc. In this case, an asterisk (*) is returned. FileHelp() makes use of the TempFile() UDF shown above. You must also create a database file called AFILES.LOD. The structure consists of a single field definition: A character field with a length of 12 and named "File". Using the DOS Path PathFind() is another way that you can make your application more iron-clad in the event that the user has no concept of what a sub-directory is. You could almost use PathFind() as a substitute for the FILE() function. FILE(), of course, returns a .T. or .F. depending on whether or not the file specified is present in the current sub-directory. PathFind() takes it one step further. If the specified file exists in the current sub-directory or ANY sub-directory in the DOS PATH, the filename with the path in front of it is returned. If the file does not exist in the current sub-directory or anywhere in the PATH, PathFind() returns a "". For example: ? GETENV("PATH") C:\;C:\DBASE;C:\DBASE\SAMPLES;C:\DOS SET DIRECTORY TO \DBASE\SAMPLES C:\DBASE\SAMPLES ? FILE("TRAVEL.DBF") .T. SET DIRECTORY TO \DBASE C:\DBASE ? FILE("TRAVEL.DBF") .F. ? PATHFIND("TRAVEL.DBF") C:\DBASE\SAMPLES\TRAVEL.DBF Function: Elapsed() FUNCTION ELAPSED PARAMETER action success = .t. DO CASE CASE action = 1 && start PUBLIC sdate,stime stime = TIME() sdate = DATE() RETURN success CASE action = 0 && end etime = TIME() edate = DATE() OTHERWISE ? "Invalid action specified" success = .F. ENDCASE hour = 1 / 24 min = hour / 60 sec = min / 60 dstart = sdate + VAL(stime) * hour + VAL(SUBSTR(stime, AT(':', stime) + 1, 2)) *; min + VAL(RIGHT(stime, 2)) * sec dend = edate + VAL(etime) * hour + VAL(SUBSTR(etime, AT(":", etime) + 1, 2)) * ; min + VAL(RIGHT(etime, 2)) * sec timedif = ABS(dend - dstart) result = IIF(dend < dstart , '-', '') rhour = INT(timedif / hour) rmin = INT((timedif - rhour * hour) /min) rsec = ROUND(( timedif - rhour * hour - rmin * min) / sec, 0) result = result + LTRIM(STR(rhour)) + ':' + ; RIGHT(STR(rmin + 100, 3), 2) +':'+; RIGHT(STR(rsec + 100, 3), 2) ? (result) RETURN success Function: TempFile() FUNCTION TempFile * Author: Dan Madoni tf = (GETENV("tmp") + LTRIM(STR(RAND(-1)*100000000,8)) +".TMP") DO WHILE FILE(tf) *-- Make sure this temp file doesn't already exist tf = (GETENV("tmp") + LTRIM(STR(RAND(-1)*100000000,8)); + ".TMP") ENDDO RETURN tf Function: FileHelp() FUNCTION FileHelp * Author: Dan Madoni PARAMETERS f_infile,f_ext IF .NOT. FILE("AFILES.LOD") *-- UDF is no good without AFILES.LOD RETURN "*" ENDIF SAVE SCREEN TO B4pop Function: FileHelp() continued *--Strip any existing file extension f_infile = IIF(AT(".", f_infile) = 0,; f_infile,SUBSTR(f_infile, 1, AT(".", f_infile) f_ext2 = "*." + f_ext f_tmp = TempFile() *-- Create a text file containing all files in the current *-- sub-directory that have the specified extension and import *-- the text file into AFILES.LOD. RUN DIR &f_ext2 > &f_tmp RESTORE SCREEN FROM B4pop SELECT 10 USE AFILES.LOD ZAP APPEND FROM &f_tmp TYPE SDF *-- Strip out the DOS messages and extensions GO TOP DELETE NEXT 4 GO BOTTOM DELETE REPLACE ALL File WITH RTRIM(SUBSTR(file, 1, AT(" ", file) - 1)) *-- Get rid of all the non-sound-alikes. DELETE ALL FOR SUBSTR(SOUNDEX(File), 2) <>; SUBSTR(SOUNDEX(f_infile), 2) PACK REPLACE ALL file WITH RTRIM(File) + "." + f_ext IF RECCOUNT() = 0 *-- No similar filenames found. ERASE &f_tmp ZAP USE SELECT 1 RESTORE SCREEN FROM B4pop RETURN "*" ENDIF *-- Pop Up resulting filenames. SET COLOR OF MESSAGES TO W+/B SET COLOR OF BOX TO G+/B SET COLOR OF HIGHLIGHT TO W+/R @ 5,34 FILL TO 20,48 COLOR W/N && Shadow DEFINE POPUP flp FROM 4,33 TO 19,47 PROMPT FIELD File ON SELECTION POPUP flp DEACTIVATE POPUP ACTIVATE POPUP flp ret = PROMPT() RELEASE POPUP flp ERASE &f_tmp ZAP USE SELECT 1 RESTORE SCREEN FROM B4pop IF LASTKEY() = 27 *-- If the user presses Esc. RETURN "*" ENDIF RETURN ret Function: PathFind() FUNCTION PathFind PARAMETERS p_file IF FILE(p_file) *-- Return immediately if exists in current directory. RETURN p_file ENDIF p_nowpath = GETENV("PATH") p_cntr = 0 DO WHILE p_cntr < LEN(p_nowpath) p_cntr = p_cntr + 1 p_temp = "" *-- Extract a directory from the PATH. DO WHILE SUBSTR(p_nowpath, p_cntr, 1) <> ";" .AND.; p_cntr < LEN(p_nowpath) p_temp = p_temp + SUBSTR(p_nowpath, p_cntr, 1) p_cntr = p_cntr + 1 ENDDO *-- Create the file name to test. p_temp = p_temp + IIF(RIGHT(p_temp, 1) <> "\", "\", ""); + LTRIM(RTRIM(p_file)) IF FILE(p_temp) *-- If the concantenated directory/file exists, RETURN it! RETURN p_temp ENDIF ENDDO *-- If we've gotten this far without RETURNing, then no such file *-- exists in the PATH. RETURN ""