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 <dos.h>.  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("<filename>","<attribute>") 
                               
  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 <RETURN> to Edit comments"
		READ
		DO CASE
			CASE READKEY() = 15 .OR. READKEY() = 271
&& <Return>
				@ 4,5 GET comments OPEN WINDOW viewit
MESSAGE;
				 "Press <CTRL-END> then <RETURN> to
save changes, <ESC> then <RETURN> to abort"
				SET CURSOR on
				KEYBOARD CHR(29) CLEAR
				READ
				SET CURSOR off
	
			CASE READKEY() = 5 .OR. READKEY() = 261	&&
<Down Arrow>
				@ 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	&& <Up
Arrow>
				@ 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
&& <Ctrl-PgUp>
				@ ln_line, 3 SAY mainline COLOR w+/gr+
				GO TOP
				ln_line = 3
				DO ReDraw
				GO TOP
	
			CASE READKEY() = 35 .OR. READKEY() = 291
&& <Ctrl-PgDn>
				@ 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	 &&
<PgUp>
				@ 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	 &&
<PgDn>
				@ 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	&& <Esc>
				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 <Esc> 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 <CTRL-END> then <RETURN> to save changes,
<ESC> then <RETURN> 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 <CTRL-END> then <RETURN> to save changes,
<ESC> then <RETURN> 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 (<Esc>
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 <RETURN> 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 (<Esc> 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 <RETURN> 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 (<Esc>
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 <RETURN> 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 (<Esc>
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 <RETURN> 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 <Space Bar> 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 <RETURN> 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<n> (where <n> 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:
		<Esc>		- Exit.

		<Return>		- Edit comments.

		<DnArrow>	- Down one record.

		<UpArrow>	- Up one record.

		<PgDn>		- Down one page.

		<PgUp>		- Up one page.

		<CtrlPgDn>	- Bottom of File.

		<CtrlPgUp>	- Top of File.
	ENDTEXT
	@ 20, 68 SAY CHR(25) COLOR w+*/r
RETURN

* EOP: Page1

****************************************************************
PROCEDURE Page2
	TEXT
	Available keys (cont'd):
		<Alt-A>		- Add and entry.

		<Alt-C>		- Search the comments field

		<Alt-D>		- 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 <Alt-D> while on a record that has a
								
blinking "*", the record will be unmarked for
								
deletion.
		<Alt-E>		- Edit the Index line

		<Alt-M>		- 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):
		<Alt-Q>		- 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 <Alt-F>.

	ENDTEXT
	@ 0, 68 SAY CHR(24) COLOR w+*/r
RETURN
* EOP: Page3
****************************************************************************
FUNCTION Color
	* Format:
	* Color( <expC> )
	*	<expC> = 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 <expC> 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 ""


