
							      9 January 1995









		 The CS-libraries

		 Version: 1.5.d




		 T.P. van den Bout
		 P.O. Box 3303
		 2280 GH Rijswijk
		 The Netherlands









    Copyright (c) 1994,1995 by	T.P. van den Bout



				  1 Contents


1 Contents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2 Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    2.1 Shareware. . . . . . . . . . . . . . . . . . . . . . . . . . . .
    2.2 Contacting the Author. . . . . . . . . . . . . . . . . . . . . .
    2.3 Registering. . . . . . . . . . . . . . . . . . . . . . . . . . .
    2.4 Legal Matters. . . . . . . . . . . . . . . . . . . . . . . . . .
	2.4.1 Disclaimer . . . . . . . . . . . . . . . . . . . . . . . .
	2.4.2 Trademarks . . . . . . . . . . . . . . . . . . . . . . . .

3 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5 Debugging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6 Runtime Libraries. . . . . . . . . . . . . . . . . . . . . . . . . . .
    6.1 Using the libraries. . . . . . . . . . . . . . . . . . . . . . .
    6.2 Compiler options . . . . . . . . . . . . . . . . . . . . . . . .

7 General Definitions & Functions. . . . . . . . . . . . . . . . . . . .
    7.1 Portability. . . . . . . . . . . . . . . . . . . . . . . . . . .
    7.2 Messages and errors. . . . . . . . . . . . . . . . . . . . . . .
    7.3 Temporary Files. . . . . . . . . . . . . . . . . . . . . . . . .

8 Buffering. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9 PAGE-Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . .
    9.2 Background . . . . . . . . . . . . . . . . . . . . . . . . . . .
    9.3 Storing data in the header-page. . . . . . . . . . . . . . . . .

10 TBASE-class . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    10.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    10.2 Using TBASE . . . . . . . . . . . . . . . . . . . . . . . . . .
    10.3 Creating a Database . . . . . . . . . . . . . . . . . . . . . .
    10.4 Opening . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    10.5 Closing . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    10.6 Appending Records . . . . . . . . . . . . . . . . . . . . . . .
    10.7 Deleting Records. . . . . . . . . . . . . . . . . . . . . . . .
    10.8 Advanced Topics . . . . . . . . . . . . . . . . . . . . . . . .
	10.8.1 Page Utilization. . . . . . . . . . . . . . . . . . . . .
	10.8.2 Locating Records. . . . . . . . . . . . . . . . . . . . .
    10.9 Miscellaneous functions . . . . . . . . . . . . . . . . . . . .
    10.10 Functions in alphabetical order. . . . . . . . . . . . . . . .

11 BTREE-class . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    11.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    11.2 BTREEx Classes. . . . . . . . . . . . . . . . . . . . . . . . .
    11.3 Multiple Keys . . . . . . . . . . . . . . . . . . . . . . . . .
    11.4 Current Pointer . . . . . . . . . . . . . . . . . . . . . . . .
    11.5 Using Btrees. . . . . . . . . . . . . . . . . . . . . . . . . .
	11.5.1 Creating. . . . . . . . . . . . . . . . . . . . . . . . .
	11.5.2 Opening . . . . . . . . . . . . . . . . . . . . . . . . .
	11.5.3 Inserting . . . . . . . . . . . . . . . . . . . . . . . .
	11.5.4 Searching . . . . . . . . . . . . . . . . . . . . . . . .
	11.5.5 Current . . . . . . . . . . . . . . . . . . . . . . . . .
	11.5.6 Deleting. . . . . . . . . . . . . . . . . . . . . . . . .
	11.5.7 Closing . . . . . . . . . . . . . . . . . . . . . . . . .
    11.6 Functions in alphabetical order.. . . . . . . . . . . . . . . .

12 CSDBGEN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    12.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    12.2 Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . .
    12.3 Features. . . . . . . . . . . . . . . . . . . . . . . . . . . .
    12.4 Limitations . . . . . . . . . . . . . . . . . . . . . . . . . .
    12.5 Definition file . . . . . . . . . . . . . . . . . . . . . . . .
    12.6 Tokenizing. . . . . . . . . . . . . . . . . . . . . . . . . . .
	12.6.1 How does it work? . . . . . . . . . . . . . . . . . . . .
    12.7 When is a substring indexed?. . . . . . . . . . . . . . . . . .
    12.8 Export to dBASE . . . . . . . . . . . . . . . . . . . . . . . .
    12.9 Exporting/Importing to/from ASCII . . . . . . . . . . . . . . .
    12.10 Starting a new database. . . . . . . . . . . . . . . . . . . .
    12.11 Opening a database . . . . . . . . . . . . . . . . . . . . . .
    12.12 Current Record . . . . . . . . . . . . . . . . . . . . . . . .
    12.13 Accessing fields . . . . . . . . . . . . . . . . . . . . . . .
    12.14 DATE fields. . . . . . . . . . . . . . . . . . . . . . . . . .
    12.15 Changing the record layout.. . . . . . . . . . . . . . . . . .
    12.16 Member functions in alphabetical order . . . . . . . . . . . .
    12.17 Warning. . . . . . . . . . . . . . . . . . . . . . . . . . . .
    12.18 Example. . . . . . . . . . . . . . . . . . . . . . . . . . . .

13 VRAM  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    13.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    13.2 Creating. . . . . . . . . . . . . . . . . . . . . . . . . . . .
    13.3 Opening & Closing . . . . . . . . . . . . . . . . . . . . . . .
    13.4 VRAM Pointers . . . . . . . . . . . . . . . . . . . . . . . . .
    13.5 Fragmentation . . . . . . . . . . . . . . . . . . . . . . . . .
    13.6 Root. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    13.7 Functions in Alphabetical order.. . . . . . . . . . . . . . . .

14 VBASE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    14.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    14.2 Using VBASE.. . . . . . . . . . . . . . . . . . . . . . . . . .
    14.3 Relocating records. . . . . . . . . . . . . . . . . . . . . . .
    14.4 Limitations.. . . . . . . . . . . . . . . . . . . . . . . . . .
    14.5 Functions in alphabetical order.. . . . . . . . . . . . . . . .

15 VBAXE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    15.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    15.2 Working.. . . . . . . . . . . . . . . . . . . . . . . . . . . .
    15.3 Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    15.4 Prototypes. . . . . . . . . . . . . . . . . . . . . . . . . . .

16 CSDIR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17 CSINFO. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

18 CSERROR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19 CSADD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    19.1 Source. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    19.2 Openings screen . . . . . . . . . . . . . . . . . . . . . . . .
    19.3 Features. . . . . . . . . . . . . . . . . . . . . . . . . . . .

20 CSTOOLS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    20.1 Introduction  . . . . . . . . . . . . . . . . . . . . . . . . .

21 CSKEYS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    21.1 CSKEYS.exe. . . . . . . . . . . . . . . . . . . . . . . . . . .

22 HEAP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    22.1 Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    22.2 When to use it? . . . . . . . . . . . . . . . . . . . . . . . .
    22.3 Using HEAP. . . . . . . . . . . . . . . . . . . . . . . . . . .
    22.4 Functions in alphabetical order.. . . . . . . . . . . . . . . .

23 Alloc-Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    23.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    23.2 Replacements. . . . . . . . . . . . . . . . . . . . . . . . . .
    23.3 Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    23.4 Memory Leaks. . . . . . . . . . . . . . . . . . . . . . . . . .

24 CSEDSTR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

25 CSWINDOWS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    25.1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . .
    25.2 General Information . . . . . . . . . . . . . . . . . . . . . .
    25.3 The C++ version, the class WINDOW . . . . . . . . . . . . . . .
	25.3.1 Syntax of the MEMBER functions. . . . . . . . . . . . . .
    25.4 The C version . . . . . . . . . . . . . . . . . . . . . . . . .
	25.4.1 Example . . . . . . . . . . . . . . . . . . . . . . . . .
    25.5 Syntax of the C functions . . . . . . . . . . . . . . . . . . .
    25.6 Working within a Window . . . . . . . . . . . . . . . . . . . .

26 CSMENU. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    26.1 General Information . . . . . . . . . . . . . . . . . . . . . .
    26.2 Defining a menu . . . . . . . . . . . . . . . . . . . . . . . .
    26.3 Connecting menus. . . . . . . . . . . . . . . . . . . . . . . .
    26.4 Displaying the menu . . . . . . . . . . . . . . . . . . . . . .
    26.5 Using menus . . . . . . . . . . . . . . . . . . . . . . . . . .
    26.6 Removing the menu . . . . . . . . . . . . . . . . . . . . . . .

27 CSPANEL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    27.1 Purpose . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    27.2 General Information . . . . . . . . . . . . . . . . . . . . . .
    27.3 Public member functions . . . . . . . . . . . . . . . . . . . .
    27.4 Dates . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    27.5 Fields. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    27.6 Using the panels. . . . . . . . . . . . . . . . . . . . . . . .
    27.7 Data validation . . . . . . . . . . . . . . . . . . . . . . . .
	27.7.1 Min and Max . . . . . . . . . . . . . . . . . . . . . . .
	27.7.2 Reset Max & Min . . . . . . . . . . . . . . . . . . . . .
	27.7.3 Templates . . . . . . . . . . . . . . . . . . . . . . . .

28 Registration Form . . . . . . . . . . . . . . . . . . . . . . . . . .
				  2 Preface


This is a shareware package consisting of mainly two distinct libraries.
The libraries are to be used with Borland C++ 3.1 or later. Both
MS-DOS and Windows applications are supported.

The libraries are intended for programmers who need some kind of
database in their application but are reluctant to use any of the vast
DBMS's. The supplied classes are doing all their IO themselves, apart
from the OS, no other software is needed.

One library concentrates on supplying a user-interface for the non-
Windows programmer, the other library supplies the classes necessary
to build database applications. An application generator is used to
produce the source for the more complicated databases with indexes
and fields.


2.1 Shareware

This software is distributed as 'Shareware' which means you are free to
copy and share these files.
It is a fully functioning version, but it does NOT include the complete
package.


2.2 Contacting the Author

You can reach me, preferably, by E-mail. The address is:
T.P.vandenBout@et.tudelft.nl.

If you don't have E-mail access you can reach me by traditional mail:
	T.P. van den Bout
	P.O. Box 3303
	2280 GH Rijswijk
	The Netherlands

For urgent matters only, phone me at +31703960172.
Please remember, it is GMT +100 over here. I have serious problems
with being woken in the middle of the night!


2.3 Registering

When you send  f 125.- (125 Dutch guilders) or $ 75 (75 American
Dollars) you will become a 'registered user'.

This means you will receive
    - the latest version
    - all the libraries
    - support.

If you want a manual, add f 30.- or $ 20.-. The manual is basically this
documentation in a printed form.


Send money/check/money order to:

		 T.P. van den Bout
		 P.O. Box 3303
		 2280 GH Rijswijk
		 The Netherlands

You will find a registration form at the end of this documentation.

2.4 Legal Matters

2.4.1 Disclaimer

EXCEPT WHEN OTHERWISE STATED IN WRITING THE
COPYRIGHT HOLDER AND/OR OTHER PARTIES PROVIDE THE
PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY
AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD
THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO
EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO
IN WRITING WILL THE COPYRIGHT HOLDER BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF
THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

2.4.2 Trademarks

IBM is a registered trademark of International Business Machines
Corporation.
MS-DOS and Windows are registered trademarks of Microsoft
Corporation.
Borland C/C++ and dBASE are registered trademarks of Borland
International Inc.











				    Part

				    One









       The next part of the documentation gives an overview of this
				package.
    It will also discuss the PAGE- and the BUFFER-class from which all
		      database classes are derived.
	  Some other topics of general interest are covered too.




				3 Introduction


This package mainly consists of two almost independent libraries. The
library called CSDB, is the far most important one. The other one, CSA,
is kind-of 'thrown in for free'. Despite this, it contains important general
purpose functions heavily used by the CSDB library.

The main purpose of this library is to provide a C++ programmer with
the utilities to incorporate databases in its applications without being
forced to use one of the huge DBMS's currently on the market. The
CSDB-library contains the classes needed to fulfil this purpose.
Therefore, the emphasis in this documentation is on the CSDB library.

The CSA library is intended to provide the non-Windows programmer
with the tools to easily build a user interface. The library therefore
contains classes for menu's, forms and windows.

As an example an address-book type of application is included. This
should give a quick impression of the purpose and capabilities of the
classes.

Although this package is shareware, it still contains a fully functioning
version. However, not all the libraries are included. What is shipped is
the library with the debug-version of the large memory model. As
explained further on, there are two versions of each library. One
containing a lot of code to trap errors, and a second one optimized for
speed.
The library in this shareware package will therefore produce somewhat
larger and slower applications then needed. Please, do keep this in
mind.

				  4 Overview

This page will try to give a quick overview of the contents of this
package.

The CSDB-library contains (among others) the following classes:
    TBASE:	    A class for reading and wrtiting records.
    BTREE:	    A btree+ to be used as an index.
    VRAM:	    A 'database' organized as a heap.
    VBASE:	    Variable length records.
    VBAXE:	    As VBASE but for very large databases.

The CSA-library contains general-purpose functions and classes to
build a character oriented user-interface. Among others, the following
classes are included:
    WINDOW:	    Text windows.
    MENU:	    A menu system.
    PANEL:	    Data entry screens.
    HEAP:	    To efficiently allocate many small blocks from the
		    heap.

Command line utilities (DOS):
    CSDIR:	    Lists the databases in a directory.
    CSINFO:	    Displays information about a database.
    CSDBGEN:	    Important program-generator.
    CSERROR:	    Utility to convert the error file to C++ source.
    CSKEYS:	    Displays the return value of the cskey() function.
    CSMALLOC:	    Tests the allocation log for memory leaks.

Documentation:
    CSDB.wp:	    This documentation in WordPerfect 6.0 format.
    CSDB.txt:	    The ASCII version.

Examples:
    CSADDRESS:	A nice address database (DOS).
    CSMENDEM:	Demonstration of the MENU class.
    CSPANDEM:	Demonstration of the PANEL class.

				 5 Debugging


Of each library two versions exist, one to be used during debugging
and one intended for normal 'production'. The difference is that in the
debug-version a lot more tests are done, and so many more errors are
reported.

The idea is to use the debug version during development and
recompile with the production version when ready. The debug version
is identical to the production version, but with additional tests. The
'working code' is 100% identical. This means there are no subtle
differences between the two versions.

The production version however can be substantial faster, up to two
times, depending on the circumstances.

To give an example:
    The TBASE class has functions to read a record from the
    database. For this function to operate properly, the class/database
    needs to be 'opened'. In the debug version this is tested with every
    call to the read function. In the production version this is never
    tested. Note that in a decently written and tested application this
    error should not occur. Errors with the open function should be
    trapped much earlier.

There are many more errors like this. Errors that should not occur when
the application is tested and (almost) ready but which can easily
emerge in the development stage.

			     6 Runtime Libraries

The libraries are to be used with the Borland C++ 3.1 compiler.
Versions exist for both MS-DOS and Windows.

With MS-DOS three memory models are supported: compact, large and
huge. These are the memory models which use far data.
The same applies for MS-Windows but with the exception of the huge
memory model. Windows doesn't allow that.


ͻ
 Name	       Operating     Library	  Memory      Type	       
	       System	     		  Model       	       
Ķ
 CSDACP       Dos	      CSA	  Compact     Production     
Ķ
 CSDACD       Dos	      CSA	  Compact     Debug	       
Ķ
 CSDALP       Dos	      CSA	  Large       Production     
Ķ
 CSDALD       Dos	      CSA	  Large       Debug	       
Ķ
 CSDAHP       Dos	      CSA	  Huge        Production     
Ķ
 CSDAHD       Dos	      CSA	  Huge        Debug	       
ͼ


ͻ
 Name	       Operating     Library	  Memory      Type	       
	       System	     		  Model       	       
Ķ
 CSWACP       Windows       CSA	  Compact     Production     
Ķ
 CSWACD       Windows       CSA	  Compact     Debug	       
Ķ
 CSWALP       Windows       CSA	  Large       Production     
Ķ
 CSWALD       Windows       CSA	  Large       Debug	       
ͼ

ͻ
 Name	       Operating     Library	  Memory      Type	       
	       System	     		  Model       	       
Ķ
 CSDDCP       Dos	      CSDB	  Compact     Production     
Ķ
 CSDDCD       Dos	      CSDB	  Compact     Debug	       
Ķ
 CSDDLP       Dos	      CSDB	  Large       Production     
Ķ
 CSDDLD       Dos	      CSDB	  Large       Debug	       
Ķ
 CSDDHP       Dos	      CSDB	  Huge        Production     
Ķ
 CSDDHD       Dos	      CSDB	  Huge        Debug	       
ͼ


ͻ
 Name	       Operating     Library	  Memory	 Type	       
	       System	     		  Model		       
Ķ
 CSWDCP       Windows       CSDB	  Compact	 Production   
Ķ
 CSWDCD       Windows       CSDB	  Compact	 Debug        
Ķ
 CSWDLP       Windows       CSDB	  Large	 Production   
Ķ
 CSWDLD       Windows       CSDB	  Large	 Debug        
ͼ





Of course you have noticed there is a system in these names:
    csOLMT.lib
    with:
	O:  Operating System
		D is DOS,
		W is Windows
	L:  Library
		A is CSA
		D is CSDB
	M:  Memory model
		C is Compact
		L is Large
		H is Huge
	T:   Type
		P is Production
		D is Debug


In the shareware distribution only two libraries are found:
    CSD.lib:	    For Dos. This is a combination of CSDDLD.lib and
		    CSDALD.lib.
    CSW.lib:	For Windows. This is a combination of CSWDLD.lib
		and CSWALD.lib.


6.1 Using the libraries

Unless you are using only the classes from the CSA collection you
always have to link in two libraries.

Example:
    Suppose you start developing in the large memory model under
    DOS. Link CSDDLD.lib and CSDALD.lib. It is best to link
    CSDDLD.lib first.

    When the application is appropriately debugged, it is time to start
    using the CSDDLP.lib and the CSDALP.lib.

    After that, you may consider porting to Windows. Assuming you
    can skip the debugging phase there, link the CSWDLP.lib and the
    CSWALP.lib.


Users of the shareware distribution have far less choice: use CSD.lib
for DOS and CSW.lib for windows.


6.2 Compiler options

The production library is compiled with:
	- No tests for stack overflow.
	- Optimized for speed.
	- No overlays.
	- 80286 instructions.
	- Signed characters.
	- Byte alignment.


The debug library is compiled with tests for stack overflow. Apart from
that it's identical.

The Windows' version is compiled with 80386 instructions.

Make sure you are using signed characters. If not, you will
experience linking problems. Don't use word-alignment.

		      7 General Definitions & Functions


7.1 Portability

A series of typedefs is used to create portable variable types.


     S8:     Singed 8 bit,   (singed char)
     U8:     Unsigned 8 bit, (unsigned char)
     S16:    Singed 16 bit   (int)
     U16:    Unsigned 16 bit (unsigned int )
     S32:    Singed 32 bit   (long)
     U32:    Unsigned 32 bit (unsigned long)

     uchar   unsigned char
     schar   signed char
     CSCHAR  char


However, on many occasions (particularly function returns) the range of
the variable is not all that much important. In these cases integers are
used because that's normally the fastest.


7.2 Messages and errors

A special set of functions is used to display errors, messages and the
alike. To avoid confusion: these are 'normal' C-type functions, not
public functions of some message class.

All these functions eventually display their messages through a call to
csmess_disp(char *).
By default (under DOS), this function writes all the messages to the
console by using the standard cputs() function.
With WINDOWS, a standard message box is called.

Fortunate, this function can easily be altered.
Before being displayed by the csmess_disp() function, all the messages
are converted into a single string. This makes changing the message
function very easy. Only a single function, which accepts a character
pointer, needs to be supplied.

The next function is intended to do that:

void csmess_set_fun( void (* fun)(char *));


//  Example (Dos):

      void display(char *s)
      {
	 //  This function is going to be used
	 //  To display messages.
	 printf("%s",s);
      }


      void main(void)
      {
	  csmess_set_fun(display);
	  //  From now on, all the messages are
	  //  displayed through a call to the
	  //  'display()' function.
     }



To restore the default, the next function can be used:

void csmess_reset_fun(void);


//  Example:

      void main(void)
      {
	  csmess_reset_fun();
	  // Restores the default
	  // Display function.
      }


void csmess_off(void);
		With this function, messages can be suppressed.
		Whether you are using the standard message
		function or has it replaced with your own, after a call
		to this function no message will be displayed.

void csmess_on(void);
		To be used in conjunction with the csmess_off()
		function. After a call to 'csmess_on()' messages will
		be displayed again.

int csmess_onoff(void);
		Returns TRUE if message displaying is switched on.
		FALSE otherwise.

void csmess_onoff(int sw);
		If called with sw unequal zero, messages will be
		displayed. Otherwise not.


7.3 Temporary Files.

Temporary files are created through the use of the cstmpname()
function, discussed in the CSTOOLS chapter.
This means, the environment variables TEMP and TMP are checked	to
determine which subdirectory is going to be used.
Temporary files can be as large as the databases they are belonging
to. So, make sure the environment variables don' t point to some small
ram-disk or an insufficient large partition.

				 8 Buffering


All the disk IO is done by a very solid buffer-system. All the database
classes have a function which makes it possible to control the amount
of memory used for buffering.

This amount is not allocated right from the start but is interpreted as a
maximum. This means that buffers are allocated on the fly, up to this
limit. The advantage is that the maximum may never be reached,
saving valuable memory for other purposes.
Of course there is also a drawback. The problem is that there is no way
the classes can predict how much memory needs to be reserved for
the remaining part of the program. The buffering-system itself will stop
allocating memory when the heap is exhausted, but if dynamic memory
allocation is used somewhere further on, the program may still
terminate with a message of the type 'out of memory'.

It should be noted that this is only a problem with the MS-DOS
operating system. Any other (real) operating system uses virtual
memory which makes sure that your program will work with any
reasonable assumption about the available amount of memory. For
performance reasons, however, it is better not to rely on virtual
memory. Let the classes do the buffering, not the operating system.

What all this means is that with MS-DOS you have to be
careful about using a large amount of ram for buffering.


				 9 PAGE-Class

9.1 Introduction

The PAGE-class constitutes a  kind of 'foundation'  for most of the
other classes in the CSDB library.  It is derived from a class 'BUFF'
which takes care of the required buffering. (Described in the previous
chapter.)

The idea is to do disk IO in chunks of 2 kB. This is close to the optimal
size for the average harddisk. These blocks are kept aligned with the
sectors of the harddisk, which improves speed considerable.
A harddisk always reads an entire sector, even if you only need, let's
say, 10 bytes. Things become even worse if the 10 bytes you are
requesting just happen to cross a sector boundary. In that case your
harddisk will read 2 entire sectors. Assuming a sector is 1024 bytes,
this means that 2*1024=2048 bytes are read just to obtain your 10
bytes!

To avoid this kind of inefficiency the PAGE-class does its disk IO in
pages of 2048 bytes while making sure every page is aligned with the
harddisk sectors. This also means that the indispensable file-header
has to be at least one sector. To avoid complications, a file-header is
used which has the same size as a page, 2048 bytes by default.

It should be noted that this entire scene is undone by using a
disk compression utility, like double space, stacker and the
alike.

Therefore, if you are concerned about performance, it is better not to
use these utilities. More over, a disk compressor will slow down your
application considerable when several files are used heavily 'at the
same time'.  This situation will almost inevitably arise with any serious
application which uses more then one database, or even a single
database with many indexes.


9.2 Background

One of the special features implemented in the PAGE-class is the
'background()' function.
This function is intended to make good use of the idle time waiting for
the user's input. It is guaranteed to return in a very short period of time,
doing at most one disk IO with every call.


By result you can write code like:

	while(!kbhit()) CLASS.background();


E.g. the background() function supplied by the PAGE class writes 'dirty'
buffers back to disk. This is done one-by-one. Each call will write at
most one buffer. Because of this, the application user will hardly notice
anything, while in the future the application doesn't have to spend time
writing buffers back to disk.

The function will return a value greater then zero if something has been
done and zero otherwise.
After some time the class runs out of things to do and it becomes
pointless to spend CPU-time on calls to the background() function. By
testing the return value, this can be avoided.

In some derived classes, the background() function is overloaded to do
additional house keeping.


9.3 Storing data in the header-page

As explained above, the header page is quite large. This page is used
to store al kind of important variables. However, there is still much
space left. Of the 2048 bytes only about 170 are used.

An application using databases is like to have some variables of his
own which need to be maintained between close/open sequences. It
seems the remaining space in the header page is a convenient place
store such data. This can save you an additional configuration file and
all the error trapping involved.

To aid in this, three functions are made public:
    int data_2_header(void * ptr,U16 length);
    int header_2_data(void * ptr,U16 length);
    U16 max_data_in_header(void);

U16 max_data_in_header(void);
		This function returns the maximum number of bytes
		which will still fit in the header page. This is simply
		the size of the header-page minus what is used to
		store the variables of the class.
		The class needs to be open.

int data_2_header(void * buffer,U16 length);
		Copies data from buffer 'buffer' to the empty space in
		the header page. The variable 'length' indicates the
		number of bytes to be copied. This figure is not
		stored anywhere. It is the programmers' responsibility
		to retrieve the right number of bytes later on.
		The class needs to be open.

int header_2_data(void * buffer,U16 length);
		The counterpart of the previous function. This
		function copies 'length' number of bytes from the
		header page to 'buffer'.
		The class needs to be open.









				    Part

				    Two








    Part Two of the documentation will explain how this library can be
	     used to build traditional relational databases.
       To do so, it uses a TBASE class to store records and a BTREE
			  class for the indexes.
       A program generator, CSDBGEN, is discussed which 'automates'
     the process of building more complex databases out of TBASE and
				  BTREE.
     These two classes can also be used seperately. In particular the
    BTREE class is very useful. It is really easy expandable and can be
     tailored to a specific purpose by supplying one single function.
     Simple databases with only one index can be build with just the
			       BTREE class.



				10 TBASE-class


10.1 Introduction

The TBASE class is intended as a simple, fast way to access records
on disk. It assumes a fixed record size and does its IO on a record-by-
record basis (contrary to field-by-field).

This means:
    1) TBASE is unaware of something like 'fields'. The idea is to use
	a C structure as record and to do all the accessing of fields
	with the standard C operators. This approach is undoubtedly
	faster then supporting access on a field-by-field basis as done
	by dBASE.
    2) No indexes. TBASE just reads or writes records, nothing else.

NOTE:	From this it is clear that with the TBASE class alone no decent
	database application can be build. Therefore, a separate
	BTREE class is supplied which can be used as an index.
	To glue it all together, a program generator, CSDBGEN, is
	available. Because C isn't particularly bright in handling strings
	and date's, the program generator takes care of that too.


10.2 Using TBASE

The next small example gives an impression of how to use the class.

As can be seen from this example, there is no 'record pointer' as in
dBASE. The functions to read and write a record, simply take an
additional parameter indicating the record number.



// A very simple example.

  # include "CSTBASE.H"

   void main(void)
   {
       typedef struct
       {
	char name[20];		// The field 'name'
	char street[40];	// The field 'street'
	long salary;		// The field 'salary'
	// All the other fields you may require.
       }record; 		// The record layout is now defined.

       TBASE db;
       record rec;
       db.open("demo.dbf",110); // Assuming the file is already created.
				// Use 110 kB for buffering.
       db.read_rec(9,&rec);	// Read record number 9 into
				// variable 'rec'.
       rec.salary=0;		// Change salary.
       db.write_rec(9,&rec);	// Write the record back to position 9.
			    // (Any other position is also possible.)
       db.close();		// Is also done automatically
				// by the class destructor.
    }





10.3 Creating a Database

Before a database can be used it has to be 'created'. This is done
through a call to the 'define()' function. Of course this is needed only
once.
Because TBASE doesn't use fields, the function takes only two
parameters: the filename of the database, and the record size.

Syntax: int define(char * name,U16 reclen);


//   This example creates a database 'demo.dbf'.


    #include "CSTBASE.H"
    void main(void)
    {
    typedef struct
    {
       char name[20];
       char street[40];
       char city[25];
    } record;

    TBASE db;
    db.define("demo.dbf",sizeof(record));
   }



10.4 Opening

Before a record can be read, the database has to be opened through a
call to the open() function.

This open() function also takes a parameter indicating the amount of
memory to be used for buffering. The memory for the buffers is NOT
allocated at the moment of the call to open(), but during the use of the
database. Memory is allocated when needed, up to this maximum.

As explained in the chapter about buffering, using up too
much memory for buffering is dangerous on an Operating
System like MS-DOS with no virtual memory.

Syntax: int open(char *name, S16 kb=32);


  // Example:
  // Opening the existing database 'demo.dbf' with 40 kB for buffers.

   #include "CSTBASE.H"
   void main(void)
   {
       TBASE db;
       db.open("demo.dbf",40);
   }



10.5 Closing

Closing the database involves writing all the buffered data back to disk
and freeing all allocated memory. The close() function is intended for
this purpose. If the close() function is not explicitly called in the
application, the class destructor will call it.

Because there can be a long interval between the last time the
database is used and the moment where the destructor is reached, it
still makes sense to call the close() function 'by hand'.

Syntax: int close(void);


    Example:

    #include "CSTBASE.H"
    void main(void)
    {
       TBASE db;
       db.open("demo.dbf",40);
       db.close();
    }



10.6 Appending Records

A special function is needed to add a record to a database: the
append_rec() function.
Note: The write_rec() can only overwrite an already existing record.

Syntax: long append_rec(void *data);
		'data' is a pointer to a record.

Syntax: long append_rec(void);
		This function can be used to add a record to the
		database without instantly filling it with a record. For
		the time being, this record will contain 'garbage'.

    // Example:

    #include "CSTBASE.H"
    void main(void)
    {
      typedef struct
      {
	 char name[20];
	 char street[40];
	 char city[25];
      } record;

    TBASE db;
    record rec;

    db.define("demo.dbf",sizeof(record));   //Create new database

    db.open("demo.dbf",40);

    strcpy(rec.name,"J.Q. Querlis ");
    strcpy(rec.street,"Avenue 120");
    strcpy(rec.city,"Bombay");

    db.append_rec(&rec);	    // The database now contains 1 record.
    db.close();
   }



10.7 Deleting Records

Deleting a record cannot be accomplished instantaneously. A 'delete
bit' is used to distinguish deleted records.

Deleting a record by setting the 'delete bit' doesn't alter much. E.g.
record 9 remains record 9 if you delete record 8.
The function 'is_delet()' has to be called to detect whether-or-not a
record is 'deleted'.

The 'pack()' function can be used to physically remove all the deleted
records from the file.

int is_delet(long r);
		Returns 0 if the record 'r' is not deleted, and 1
		otherwise.

void delet(long r);
		Sets the delete bit for record 'r'.

void undelet(long r );
		Resets the delete bit for record 'r'.

void pack(void);
		Removes all the records with the delete bit set from
		the database. This is done without the use of
		temporary files.


10.8 Advanced Topics

The next paragraph can be skipped by first-time readers.

10.8.1 Page Utilization
The TBASE class does its IO in pages of 2 kB. It fits an integer number
of records on these pages. This approach can lead to a large chunk of
unused space on the pages, particularly if you are using large records.
On average the slack will be a half record, but if you have a record of
1100 bytes almost 1 kB will be wasted!

Solution:
    This waste of disk space can be avoided by using pages which
    have the same size as the record. This means that the pages will
    no longer be aligned with the sectors of your harddisk!

The function to accomplish this is:  smallest_page().

void smallest_page(void);
		The function has to be called before the define()
		function. Because this changes the entire layout of
		the database file, this cannot be altered once the
		database is created.


    // Example:

    #include "CSTBASE.H"

    void main(void)
    {

       typedef struct
       {
	  char name[20];
	  char street[40];
	  char city[25];
       } record;

    TBASE db;

    db.smallest_page(); 		//Before the define!

    db.define("demo.dbf",sizeof(record));

   }



10.8.2 Locating Records
In the examples given so far, a record is first read into a local variable
and, after being altered, written back to disk. It seems there is room for
improvement here. After the record is copied into the local variable it is
in memory TWICE, once in the variable and another time in the
database buffers.

If you know what you are doing, a significant performance increase
can be gained from obtaining a pointer directly into the buffer system.

The 'locate_rec()' function does just that. When you are working in the
database buffers through a pointer, there is no way the buffer system
can tell if you are altering anything. Therefore, it's the programmers'
responsibility to indicate whether or not modifications are going to take
place.

char *locate_rec(long rec);
		This function returns a pointer to record 'rec'. It
		assumes that no alterations are going to take place.
		This means that the buffer is not written back to disk!

char *locate_rec_d(long rec);
		The additional '_d' stands for Dirty buffer. The
		function returns a pointer to record 'rec'. It is
		assumed alterations ARE going to take place. The
		buffer is therefore written back to disk when space is
		needed to store another page.


IMPORTANT!!
The locate functions return a pointer directly into the buffer
system. Nothing less and nothing more. Any member function
of the same class instance which MAY cause disk IO, can
therefore alter the contents of the buffers, making your pointer 'point' to
an entirely different record! When using these functions, it is highly
advisable to do all the reading or writing to the record before calling
any other TBASE member function.
Special care should be taken in using the 'background()' function.

If you are not sure you fully understand this, don't use the locate
functions.





    // Example, of the locate_ function.

    #include "CSTBASE.H"

    void main(void)
    {

    typedef struct
    {
       char name[20];
       int age;
    } record;

    TBASE db;
    record *rec;

    db.define("demo.dbf",sizeof(record));
    db.open("demo.dbf");                    // Use default 32 kB for buffers.

    for(int i=1;i<=12;i++) db.append_rec(); // Append 12 records.

    rec=(record *)db.locate_rec_d(7);	    // Obtain pointer to record 7.
				// '_d' because we will 'write'.

    rec->age=34;		// That's all it takes to make an
				// alteration. No need for a
				// 'write' function.
    db.close(); 		// Not strictly necessary.

   }



10.9 Miscellaneous functions

long numrec(void);
		Returns the number of records currently in the
		database.

U16 lengthrec(void);
		The size of the record in bytes.

10.10 Functions in alphabetical order.

Function prototypes are in 'cstbase.h'.


S32 append_rec(void *data);
		Append a record to the database. The newly created
		record is filled with the data from buffer 'data'. The
		function returns the number of the new record (which
		is equal to the number of records in the database).
S32 append_rec(void);
		Same as the previous function, only this time the new
		record is filled with binary zero's.
int close(void);    Closes the database. If the database is already
		    closed, nothing happens. TRUE is returned on
		    success, FALSE otherwise.
int define(char *name,U16  reclen);
		Creates a TBASE file named 'name'. The parameter
		'reclen' indicates the size of the records. TRUE is
		returned on success, FALSE otherwise.
void delet(S32	rec);
		Sets the delete bit of record 'rec'.
int empty(void);Removes all the records from the database.
		Afterwards there will be zero records left, but the
		database will still be open. TRUE is returned on
		success, FALSE otherwise.
int is_delet(S32  rec);
		Returns the value of the delete bit of record 'rec'.
		TRUE means the delete bit is set, FALSE means the
		bit is not set.
U16 lengthrec(void);
		Returns the length of a record. Because TBASE
		works with fixed length records, this value is the
		same for all records. It's the same value used in the
		call to define().
char *locate_rec(S32  rec);
char *locate_rec_d(S32	rec);
		Functions to return a pointer to record 'rec' directly
		into the buffer system. Please read the paragraph
		about this topic before using these functions.
S32 numrec(void);
		Returns the number of records currently in the
		database. Whether or not a record is marked for
		deletion makes no difference.
int open(char *name,S16 kb=32);
		Opens the existing database 'name' while using 'kb'
		Kb ram for buffering. TRUE is returned on success,
		FALSE otherwise.
int open(void); Returns TRUE if the database is open, FALSE
		otherwise.
int pack(void); Removes all the records with the delete bit set from
		the database. This is doen without the use of
		temporary files. TRUE is returned on success, FALSE
		otherwise.
void read_rec(S32 rec, void *buff);
		Copies the contents of record 'rec' into buffer 'buff'.
int save(void); Writes all buffered data back to disk. The database
		header block is also updated. The database remains
		open. TRUE is returned on success, FALSE
		otherwise.
void set_delet(S32  rec,int ToF);
		Changes the value of the delet bit of record 'rec'. If
		'ToF' is TRUE, the delete bit is set, otherwise it is
		reset.
void smallest_page(void);
		Set the page size to its smallest possible value. That
		is, a page will have the same size as a record. This
		means pages will no longer be alligned with the
		harddisk sectors. The function has to be called before
		the define() function. It changes the entire layout of
		the database file so it cannot be changed once the
		file is created.
void undelet(S32  rec);
		Resets the delete bit of record 'rec'.
void write_rec(S32 rec, void *buff);
		Overwrites the contents of record 'rec' the data from
		buffer 'buff'. Record 'rec' needs to exist, the function
		connot be used to append a record.






				11 BTREE-class


11.1 Introduction

This is propably not the place to give a formal definition of a btree, but
it cannot hurt to give some background information.

A btree is a system, developed several decades ago, to store data in
some predetermined order. The btree is capable of maintaining this
order even under insertions and deletions.
It is possible to use btrees for data in ram, but they were designed (and
so optimized) to work with data on disk. Btrees are capable of locating,
inserting or deleting a specific record with only a few disk-IOs, even
when they have become very big.

Of course there is a price to be payed for this: the disk space occupied
by the btree is not fully utilized. Worst case, only 50% of the space is
used. Fortunate, in the average case 75% is used.

Btrees are convenient for indexes on a database. Of a record in the
main database, the key field and the corresponding record number are
stored together in the btree.
When a certain record is needed, the btree is capable of quickly
(without much disk IO) locating the required key value because the
btree keeps them sorted in that order. When the key field is located in
the btree, it is instantly clear which record from the main database has
to be read from disk.

There are several 'flavours' of btrees. The one implemented in this
library is known as a 'btree+'.


11.2 BTREEx Classes

Unfortunate, it's a little cumbersome to use one BTREE class for every
type of data. Therefore, there are several minor variations on the main
BTREE class to account for the different variable types supported by C.
Each type has its own class.

Classes:
    BTREEb  For binary data.
    BTREEi  For integers.
    BTREEl  For longs.
    BTREEc  For characters.
    BTREEf  For floats.
    BTREEd  For doubles.
    BTREEa  For ASCII data. (Strings)

All these classes are derived from the BTREE class. Mainly, they differ
only in one function. This is the function needed to compare keys. You
can easily define a new BTREE class for new type of variable. The only
thing to do is define a int t_key(void *a,void *b) function which returns:

	>0    if  a>b
	 0    if  a==b
	<0    if  a<b

with 'a' and 'b' pointers to the new type of variables.


Example:
Say, you have your data stored in a C structure which
you want to kept sorted on one of its fields.

Something like:

      typedef struct
      {
	char name[20];
	int  number;
      }

Which you want to have sorted on the 'number' field.

class BTREEnew: public BTREE
{

    virtual int t_key(void *a,void *b)
    {
       return ((record *)a)->number-((record *)b)->number;
    }
    virtual int class_ID(void)	{ return -100; }
 };


That's all!
The value '-100' in the class_ID() function is not all that important. Its
purpose is to give other library functions the opportunity to distinguish
between the classes. The value just has to be different from any value
the other classes have. This can easily be accomplished by choosing a
negative number.


11.3 Multiple Keys

Because of its nature you would expect a 'key' to appear only once in a
btree. However, on many occasions it turns out there is a need for
storing the same key more then once, but with a different data part.
E.g. this happens when you use a btree as an index on a database and
in  the field  you are indexing a certain value appears more then once.
In that case, you want to store the key value several times but with a
different data part, namely the record number in the database.

By default a key value can appear only once in the btree. If you try to
insert a second entry with the same key value, it will simply replace the
existing one. The option 'multiple_keys()' can be set  to alter all that.

Syntax: void multiple_keys(int	YesNo);
	void multiple_keys_YES(void);
	void multiple_keys_NO(void);


When the function 'multiple_keys()' is called with the argument set to
TRUE, the btree will store a key value more then once.

It is important to realize that the btree only keeps the key
values sorted, NOT the data values. This means that when
you are searching for a particular key/data value, the btree is
capable of quickly locating the required key but has to find the correct
data value by sequentially traversing all the inserted data belonging to
that particular key.

Btrees are intended to give quick access to key values by keeping
them sorted, this does not apply to data values. Expecting anything
else is misusing the btree. If you want quick access to a large number
of different data values, all belonging to the same key, you need a
different approach. The best thing to do is to construct a new btree and
use a key which is a concatenation of the original key and the original
data part.

Setting the multiple_keys option has to take place before the 'define'.


// Example

#include "csbtree.h"

void main(void)
{

    typedef struct
    {
	char name[20];
	int  age;
    } record;

    BTREE bt;

    bt.multiple_keys_YES(); // Must be called before 'define'
    bt.define("btree.dat",sizeof(record),sizeof(long));

    // By now a btree 'btree.dat' is created in the current working
    // directory with the multiple-keys option switched on.

}




11.4 Current Pointer

Contrary to TBASE, the btree class does use a 'current' pointer. When
using btrees, the need to obtain the next (or previous) entry arises so
often it's inevitable.

The btree class spends as little time as possible on maintaining this
current  pointer. Therefore you should assume it is NOT set, unless you
have strong reasons to believe otherwise.

A limited set of functions can be used to set the current pointer. After
that, the 'next()' and the 'previous()' functions can be used to move to
the next resp. the previous entry.
When these functions 'fail', which can be noticed from their return
value,	you should assume the current pointer is not set (any more).

The next functions can be used to set the current pointer:
- all the search functions.
    E.g.  search_gt(),	find() etc.
- all the min() and max() functions.
    E.g. max_key(), min() etc.
- the insert() function.

The current pointer can be moved back and forth with the next() and
previous() functions.

Once the current pointer is set, the 'current()' functions can be used to
obtain the key value and/or the data part.

Any other function can, and probably will, render the value
of the current pointer undefined!



// Example
// The next example displays the contents of a btree with
// 'strings' as key fields.
// It assumes that the btree 'demo.dbf' exists and that the
// key fields are less then 100 bytes.

#include "iostream.h"
#include "csbtree.h"

void main(void)
{
     char  buffer[100];
     BTREEa bt;

     bt.open("demo.dbf",250);  // Does not set the current pointer.

		      // Make the first entry the 'current'.
     if(bt.min())     // This returns FALSE only if the btree is empty.
     do
     {
	 bt.current_key(buffer);  // Read the 'current' key value.
	 cout<<buffer<<endl;	  // Display it.
      } while(bt.next());	  // Move the current pointer 1 position.

      bt.close();
}



11.5 Using Btrees

The next paragraph will try to sort the public memeber functions
according to their purpose.

11.5.1 Creating

    void multiple_keys_YES(void);
    void define(char *name,int key_length, int data_length);

11.5.2 Opening

    int open(char *name, int kb_buffer);

11.5.3 Inserting

    void insert(void *key,void *data);

11.5.4 Searching

    int   search(void *key,void *Data);
    int   search_gt(void *key,void *Key,void *Data);
    int   search_ge(void *key,void *Key,void *Data);
    int   search_lt(void *key,void *Key,void *Data);
    int   search_le(void *key,void *Key,void *Data);
    int   search_dat_..(void *key,void *Data);
    int   search_key_..(void *key,void *Key);
    int   find(void *key,void *data);
    int   find(void *key);
    int   max(void *Key,void *Data);
    int   min(void *Key,void *Data);

11.5.5 Current

    int   current(void *Key,void *Data);
    int   current_key(void *Key);
    int   current_dat(void *Data);

11.5.6 Deleting

    int   delet(void *delete_key);
    int   delet(void *delete_key,void *data_value);

11.5.7 Closing

    void  close(void);
11.6 Functions in alphabetical order.

The function prototypes are in "CSBTREE.H".

void  close(void);
		Closes the btree after use. All buffers are flushed and
		all the allocated memory is freed. This function is also
		called by the class destructor if needed.
int   current(void *Key,void *Data);
int   current_key(void *Key);
int   current_dat(void *Data);
		Returns the key and/or data part of the entry the
		current pointer is pointing at.
		Parameters:
		Key Pointer to the buffer to which the key value
		    has to be copied.
		Data	Pointer to the buffer to which the data part
			has to be copied.
		If the current pointer has not been set, the functions
		return FALSE and no data is written into the buffers.
		If the current pointer is set, the functions return TRUE
		and the appropriate data is copied into the buffers.
void define(char *name, int key_length,int dat_length);
		This function is needed to initially create the file and
		properly setup all variables. This is only needed once,
		before the first call to open().
		Parameters:
		name	    The filename which is going to be used.
		key_length  The number of bytes in the key. This
			    parameter is even needed when its
			    value is 'obvious' from the btree type.
		dat_length  The number of bytes in the data part.

    Example:

    If you want to use a btree as index on a string field
    in a database you will need:
    - a btree of type ASCII: BTREEa.
    - a key_length equal to the length of the string field
      in the database record.
    - a data part which is capable of holding the number
      of the record in the database. This will normally be
      a 'long'.

    #include "cstbase.h"
    #include "csbtree.h"

    void main(void)
    {
	typedef struct
	{
	   char   name[30];
	   float   income;
	}record;

	TBASE  db;
	BTREEa index;

	db.define("demo.dbf",sizeof(record));
	index.define("demo.ndx",30,sizeof(long));

    }



int   delet(void *delete_key);
		Searches for key 'delete_key' and, if present,
		removes it from the btree. If the option multiple_keys
		is set to 'yes' there can be more then one entry with
		the required key. Under that circumstances, all these
		entries will be removed. The function delet() can also
		accept a parameter with the value of the data part.
		That function should be used for deleting when
		multiple keys are used.
		The function returns TRUE is something is deleted,
		FALSE otherwise.
int   delet(void *delete_key,void *data_value);
		The same as the previous delete function but this
		time the value of the data part is also specified. Only
		the entry which matches both the key and the data
		part is removed from the btree.
void  empty(void);
		Removes all the entries in the btree. Upon return, the
		btree will contain zero keys.
		The btree needs to be open, and will remain open
		afterwards.
int   find(void *key,void *data);
int   find(void *key);
		These functions 'test' if a certain key value is present
		in the btree. When a btree is used with multiple keys,
		it can be necessary to specify the data part to
		uniquely identify an entry.
		TRUE is returned when the required entry is found,
		FALSE otherwise.
void  insert(void *key,void *data);
		Inserts a new entry in the btree. 'key' is a pointer to
		the key field and 'data' is a pointer to the data part.

    Example:

    void main(void)
    {

	typedef struct
	{
	   char   name[30];
	   float   income;
	}record;

	TBASE  db;
	BTREEa index;

	db.define("demo.dbf",sizeof(record));       //Creating.
	index.define("demo.ndx",30,sizeof(long));   //Creating.

	db.open("demo.dbf",30);         // Opening the database with 30 Kb buffers
	index.open("demo.ndx",80);      // Opening the index with 80 Kb buffers.

	record rec;

	strcpy(rec.name,"John Wayne");
	rec.income=7000;		// Filling the record.

	long recnr=db.append_rec(&rec);     // Insert in the database.
	index.insert(rec.name,&recnr);	    // Update the index.

	db.close();		    // Close the database.
	index.close();		    // Close the index.

    }


int   max(void *Key,void *Data);
int   max_dat(void *Data);
int   max_key(void *Key);
int   max(void);
		These functions return the last (highest) entry in the
		btree. The parameters 'Key' and 'Data'  have to be
		pointers to respectively a buffer for the key part and a
		buffer for the data part. These buffers will be filled
		with the appropriate data upon function return.
		The functions max_dat() and max_key() can be used
		when only one of the two parts is required.
		TRUE is returned on success, FALSE otherwise.
int   min(void *Key,void *Data);
int   min_dat(void *Data);
int   min_key(void *Key);
int   min(void);
		These functions return the first (lowest) entry in the
		btree. The parameters 'Key' and 'Data'  have to be
		pointers to respectively a buffer for the key part and a
		buffer for the data part. These buffers will be filled
		with the appropriate data upon function return.
		The functions min_dat() and min_key() can be used
		when just one of the two parts is required.
		TRUE is returned on success, FALSE otherwise.
void  multiple_keys(int TrueOrFalse);
		With this function the use of multiple keys is
		controlled. For more information about multiple keys
		please read the paragraph about the subject.
		Multiple_keys(TRUE) will allow for multiple keys.
		Multiple_keys(FALSE) will not allow for multiple keys.
		Important:
		This function has to be called before the define()
		function is invoked. It is not possible to alter the
		setting of the multiple key parameter later on.
int   multiple_keys_YES(void);
		Same as multiple_keys(TRUE);
int   multiple_keys_NO(void);
		Same as multiple_keys(FALSE);
int   multiple_keys(void);
		This function returns TRUE if multiple keys is set to
		'YES' and FALSE otherwise.
int   next(int n);
int   next_key(int n,void *Key);
int   next_dat(int n,void *Data);
int   next(int n,void *Key,void *Data);
		A set of functions to move the current pointer closer
		to the 'end' of the btree.
		Apart from that, they are similar to the prev() funtions.
		For more information, please see over there.
int	next(void);
		Same as next(1); but more efficient.
long  numkey(void);
		This function returns the number of entries which
		exists in the btree.
int   open(char *name, int kb_buff);
		Opens an existing btree 'name'. The parameter
		'kb_buff' indicates how many Kb ram has to be used
		for buffering.
		The function returns TRUE on success, FALSE
		otherwise.

    //Example:

    #include "csbtree.h"

    void main(void)
    {

       BTREEa index;
       index.open("demo.ndx",100);

    }

		This will open the btree 'demo.ndx' and will, at most,
		use 100 Kb ram for buffering.
		NOTE:	read the chapter about buffering, before
			using really large amounts of buffers.

void  pack(void);
		A function which optimizes disk usage. Due to many
		insertions and deletions it is possible for blocks with
		zero keys to emerge. There are no pointers to these
		blocks but they will still be part of the btree because
		they can not be removed unless they are the last
		block in the file. These blocks will be used as soon
		as the need for a new block arises.
		The pack function will remove these empty blocks
		and rearrange all the keys. This is done by writing all
		the data to a temporary file and reload the btree.
		Afterwards the btree will be (close to) 100% full.
int   prev(void);
int   prev(int n);
int   prev_key(int n,void *Key);
int   prev_dat(int n,void *Data);
int   prev(int n,void *Key,void *Data);
		A set of functions to move the current pointer closer
		to the 'beginning' of the btree.
		Parameters:
		n	The number of entries the current pointer
			needs to be moved.
		Key A pointer to the buffer which is going to be
		    filled with the value of the key field.
		Data	A pointer to the buffer which is going to be
			filled with the value of the data part.
		When the current pointer is NOT moved, the key and
		data buffers will not be filled. Otherwise they will be
		filled with the values of the entry to which the current
		pointer is moved.
		The key field, the data field, or both can be obtained
		by selecting the appropriate function.
		The prev(void) function is a much more efficient
		version of the prev(1) function.
		Important:
		This current pointer needs to be set first. Please,
		read the praragraph about the 'current pointer' for
		more information.
		When one of the prev() functions is called while the
		current pointer is not set, the function will return 0.
		This current pointer can not be moved before the
		beginning of the btree. Therefore the number of
		positions moved can differ from the number
		requested.
		The return value is the number of positions the
		current pointer has actually moved.
int   search(void *key,void *Data);
		Searches for 'key' and fills buffer 'Data' with the
		corresponding data part if 'key' is found.
		The function returns TRUE if found, FALSE
		otherwise.
int   search_gt(void *key,void *Key,void *Data);
int   search_ge(void *key,void *Key,void *Data);
int   search_lt(void *key,void *Key,void *Data);
int   search_le(void *key,void *Key,void *Data);
		The purpose of this set of functions is to search for a
		key value next to a given value. The names of the
		output parameters start with a capital while the input
		parameters are all lower case.
		The suffix '_xx' has a meaning conform to the
		corresponding FORTRAN operators:
		gt: Greater Then	>
		ge: Greater Equal	>=
		lt: Less Then		<
		le: Less Equal		<=
		The functions return TRUE if such a key could be
		found, FALSE if not.

     Example:
     Assume the next table represents a btree.

	 Entry	  Key value    Data value

	  1	Blue		123
	  2	Green	    45
	  3	Red	    678

       search_gt("Blue",Key,Data)
	Return value:	TRUE
	Key:		Green
	Data:	       45

       search_ge("Blue",Key,Data)
	Return value:	TRUE
	Key:		Blue
	Data:	    123

       search_lt("Blue",Key,Data)
	Return value:	FALSE
	Key:		undefined
	Data:	    undefined

       search_ge("Orange",Key,Data)
	Return value:	TRUE
	Key:		Red
	Data:		678


int   search_dat_..(void *key,void *Data);
int   search_key_..(void *key,void *Key);
		The previous described functions return both the key
		value and the data value.
		In some cases this will be a waste of memory,
		therefore there are two similar sets of functions,
		which either return the found key value or the data
		part.
		search_dat_..() returns only the found data value.
		search_key_..() returns only the found key value.

    Example:

    With the same btree as in the previous example


       search_dat_gt("Blue",Data)
	Return value:	TRUE
	Data:		45

       search_key_ge("Blue",Key)
	Return value:	TRUE
	Key:		Blue


int   skip(int n);
int   skip(int n,void *Key,void *Data);
int   skip_key(int n,void *Key);
int   skip_dat(int n,void *Dat);
		A set of functions to move the current pointer. These
		functions are front ends for the next() and prev()
		functions. E.g. this is how the skip_key() function is
		implemented:


		int skip_key(int n,void *Key)
		    {  if(n>0) return  next_key( n,Key);
		       else    return -prev_key(-n,Key); }


		So, with these functions the argument 'n' may be
		positive or negative.
		With 'n' positive the current pointer is moved to the
		'end' of the btree. This is done by calling next(n).
		With 'n' negative the current pointer is moved to the
		'beginning' of the btree. This is done by calling
		-prev(-n).
		The return value can be either positive or negative
		depending on the value of 'n'. When 0 is returned, the
		current pointer has not been moved or was not set.
		See also the prev() functions for more information.
void  zap(void);
		Closes the btree (when needed) and re-establishes
		all defaults. In other words: the zap() function
		restores the status of the btree immediatly after the
		class constructor. Its purpose is to open (or create) a
		new btree, starting of with all the parameters set to
		their default values.
				  12 CSDBGEN


12.1 Introduction

CSDBGEN is a program generator.

So far we have discussed the TBASE class which is capable of reading
and writing records and the BTREE class which can be used as an
index. CSDBGEN is needed to make a nice database out of this, which
is capable of manipulating fields, maintaining indexes and the alike.

It takes as input a 'definition file' which describes the fields and the
indexes. From this it produces the source for a new C++ class. Member
functions of this newly created class are used to access the fields.

CSDBGEN does not generate a user-interface.

There are several good reasons for using a program generator.
- The TBASE class can concentrate on manipulating records rather
    then fields. Because of that, it remains a universal and efficient
    way to do disk IO.
- With this approach it is easier to deal with field types that are not
    supported  by the C programming language, particularly dates.
- It is relatively easy for the program generator to convert to dBASE
    format because it has all the required knowledge at hand. Figuring
    out this conversion during runtime is a lot more complicated and
    will also make your executable larger because the knowledge to do
    the conversion is in the application instead of in the program
    generator.
- Or more in general: everything the program generator does, can be
    left out from the application, making the executable smaller.
- Without a program generator, the differences between the field types
    have to be dealt with runtime, perhaps even with every call
    accessing a field. Doing so, will inevitable result in some sort of an
    interpreter. Interpreters have two major drawbacks. First they are
    amazingly slow, and second they contain functions to handle every
    possible case. This means code is linked in for every known field
    type, even if it's not used.

12.2 Overview

Using CSDBGEN starts of with creating a definition file. This file
describes the layout of a record, the indexes, the name of the new
class and the name of the files.

When this file is created, CSDBGEN is called with this filename as a
parameter. In return you will obtain the source for a brand new C++
class. This source is ready to compile, without the need for manual
editing.

This new class has public member functions for things like reading a
field, setting the active index, exporting to ASCII, exporting to dBASE,
reindexing, packing and so on.
These member functions are very easy to use because they take very
few, and often none, parameters. This is possible because a lot of the
information which is specific for the database is already in the source of
the functions.

The generated class controls one database and all the indexes that
come with it. If you need more then one database, as is to be
expected, you have to repeat this procedure for the other databases as
well.

CSDBGEN does not aid in building the user-interface.

An elaborate example is at the end of this chapter.

12.3 Features

- Indexes with more then one reference to a record!
    This is an innovation! It makes it possible to locate a record by
    searching for a substring in the field, rather then the entire field.
    This topic is discussed in more detail in the paragraph 'tokenizing'.
- Conversion to-and-from ASCII.
    This is convenient for backups, conversions to other systems and
    also for making changes in the record layout.
- Export to dBASE compatible file format.
    CSDBGEN generates a function capable of writing out the contents
    of the database to a file which can be read by dBASE.
- Can manage very large databases.
    The design of the libraries has been keen on avoiding limitations.
    By result, databases up to 2 billion records are theoretically
    feasible.
- No overhead.
    Due to the use of a program generator, the overhead involved in
    accessing fields is next to none.
- Large buffers.
    This system is capable of effectively using large amounts of RAM
    for buffering.
- Fast.
    The precious two points, together with the efficient BTREEs
    guarantee a very fast database.


12.4 Limitations

- No record locking.
- No multi-user support.
    This system was designed to be used in single user applications.
    Time being, there is no support for network/shared databases.
    Perhaps there will be in the future but if so, it will take the form of
    a new series of classes.


12.5 Definition file

The information needed to generate the new class is obtained from a
'definition file'. To get you started, the CSDBGEN utility is capable of
generating an example.


example

c:\borlandc>csdbgen /example>example.def

Something like this will generate an example definition file 'example.def'.



To get acquainted, let's look what's in it.

Example definition file:

    class:  NAM
    record: NAM_record
    file:   demo
    field:  name	s   30	Y
    field:  city	s   20
    field:  birthday	d	Y
    field:  salary	f



Explanation:

    line 1: class:  NAM
	The program generator generates a class, which of course has
	to have a name. In this example 'NAM' .

    line 2: record: NAM_record
	As explained before, the database system uses a C structure
	as a record. The name of this structure is defined in the
	second line.
	In the generated header file the following (among others) will
	appear:


	 #define NAME_LENGTH	  30
	 #define CITY_LENGTH	  20

	 typedef struct
	 {
	    char   _name[NAME_LENGTH+1];
	    char   _city[CITY_LENGTH+1];
	    long   __birthday;
	    float  _salary;
	 } NAM_record;


    line 3: file:   demo
	This line indicates the name of the files the database system
	is going to use. In this example three files will be used:
	    - demo.dbf, the TBASE database file.
	    - demo01.idx, the BTREE index file on field 'name'.
	    - demo02.idx, the BTREE index file on field 'birthday'.


    line 4/7:
	Field definitions.
	The syntax for a field definition is:
	field: <field_name> <field_type> [length] [format] [index]
	With:
	    field_name, the name of the field.
	    field_type, the type of the field which can be:
		i:  integer
		l:  long
		f:  float
		F:  double
		c:  character
		s:  string
	    length, only for strings.
		Indicates the number of characters the field needs to
		be able to store. One additional byte is reserved to
		store the null terminator.
	    format, only for date fields.
		Please, see the documentation on DATE fields.
		To give a quick example: MDY4 means, Month, Day
		and 4 positions for the Year.
	    index,
		'Y' means a normal index.
		'T' means a 'tokenized' index.
		 Nothing means no index at all.
		See also the documentation on 'tokenizing' further on.

In the example:

    field:  name    s	30  Y
	A field 'name' of type string. 31 Bytes are reserved, 30 for the
	characters and 1 for the null terminator. An index is
	maintained for this field because of the additional 'Y'.

    field:  city    s	20
	A field 'city' of type string with a length of 20 characters. There
	is NO index on this field.

    field:  birthday	d   Y
	A field 'birthday' of type DATE. An index is placed on this field.

    field:  salary  f
	A field 'salary' of type float, without an index.


12.6 Tokenizing

This is a new concept!

Let's look at an example to make things clear.

The demonstration database CSADDR, which comes with this package,
uses tokenizing on its 'name' field.

Let's say you have entered a record for the 'World Health Organization'.
And now, for the first time in your life, can locate this record by entering
'health'. In fact, because CSADDR uses something called incremental
search, entering just 'hea' is probably sufficient to pop up the record.
It's important to know that this feature is not implemented by traversing
the entire database from the first record to the last, as is done by some
toy-applications.


12.6.1 How does it work?

Traditionally, the index stores the entire field. In this example that would
mean 'World Health Organization' is stored in the index together with a
reference to the record number in the main database.
With tokenizing, the index will store 3 entries, namely 'World', 'Health'
and 'Organization'. This approach means that there will be a lot more
entries in the btree then there are records in TBASE.

In other words, an index is maintained on every suitable substring,
rather then the entire field.


12.7 When is a substring indexed?

First of all: tokenizing only applies for string fields. E.g. 'Tokenizing' a
float field seams pointless.

Whether or not a substring is put in the index is controlled by two
things:
- the way it is separated from the rest of the field.
- the length of the substring.

CSDBGEN generates a function 'tokenize'. This function contains a
string 'delim' and a constant 'min_len'.


// A part of the 'tokenize' function.

    char delim[]="\t,() ";  // Token delimiters
    const min_len=4;	    // Minimum length for a token to be indexed



The tokenize function separates the field into substrings according to
the characters in the 'delim' array. Notice that the '.' is not a delimiter.
This is to prevent abbreviations from being split up.
A substring has to be at least four bytes long to appear in an index.
This is not too long for most cases but it means that three letter
abbreviations like 'IRS' are not indexed.

Of course you can alter these two variables when needed.


Example definition file:

  class:    NAM
  record:   NAM_record
  file:     demo
  field:    name	s   30	T
  field:    city	s   20
  field:    birthday	d
  field:    salary	f


Notice the 'T' behind the 'name' field. This is short for 'tokenize'. The
generated class will maintain an index for the name field with
references for every suitable substring.

12.8 Export to dBASE

The program generator also produces a  member function:

    int to_DBASE(char *filename);

When called, this function produces a file 'filename' which can be read
by dBASE. Index files are NOT written.
At this moment there is no corresponding function to do the conversion
the other way round.


12.9 Exporting/Importing to/from ASCII

    int export(char *filename);

This writes out the contents of the database to an ASCII file 'filename'.
That file will also contain information about the fields. In this way the
import() function knows how to process this data, even after changes in
the record layout.

    int import(char *filename);

This member function reads the ASCII file 'filename' and appends its
data to the current database. It is meant to be used in conjunction with
the export() function. The export function starts of with writing the entire
definition file. The import function uses this information to skip fields
which are not in the database and to read fields in the right order. Only
fields which are also in the current database are read, the others are
ignored. This mechanism can be used to make changes in the record
layout.


12.10 Starting a new database

A member function

    void define(void);

is available to create a new database. If the database already exists, it
is overwritten!


12.11 Opening a database

The member function

    void open(void);

opens an existing database. To avoid errors, a blank record is inserted
when an empty database is opened.
Index files are automatically rebuilt if they don't exist.


12.12 Current Record

At any moment there is always a record the 'current record'. The
functions to read and write fields all work with this current record.

- After opening, the first record becomes the current record.
- The go_to(), skip(), top(), bottom() and the search()  functions can be
    used to make another record 'current'.


12.13 Accessing fields

CSDBGEN generates two member functions for each field. One to read
the field, and a second to write the field. The names are the same but
the arguments differ.



Example:

// A part of the definition file:
      class: NAM
      record: nam_record
      file: dbtest
      field: name s 40
      field: number i

// We have a 'name' field consisting of a string with 40 characters
// and a 'number' field which is an integer.
// Among others, the next member functions are generated by CSDBGEN:

class NAM
{
public:
// For reading
      char  *name(void);
      int	number(void);
// For writing
      void   name(char *s);
      void   number(int i);
};


The next example gives an impression of how the generated class
could be used.


Example:

   void main(void)
   {
      NAM db;		// We now have a class 'NAM'.

      db.open();	// Open database. Assuming it already exists.
			// No file names have to be entered.
			// All the indexes are opened automatically.

      puts(db.name());	// Displays the name field of the first record.
			// After 'open' the first record is 'current'.

      db.name("Pjotr Idaho");   // Changes the contents of the 'name' field to
			// 'Pjotr Idaho'. Indexes are updated automatically.

      db.close();	// Close database.
   }




12.14 DATE fields

Standard C doesn't support date variables. Therefore, this library has
its own DATE class.

The functions to read and write date-fields are using a string
representation of a date. These strings can represent a date in several
formats. CSDBGEN uses a default of DMY4. This means 2 positions for
the Day, 2 positions for the Month and 4 positions for the Year.


Example:

   "02/04/1994"    // By default interpreted as:  April the 2th 1994.


When a two position representation of the year is wanted, use Y2
instead of Y4.
Every order of M,D,Y2 or Y4 is acceptable.


Example:
    If you want "02/04/94"  to be interpreted as February the 4th 1994, use the format
    MDY2. The line in the database definition file has to be:

    field: birthday d MDY2

    If you want the field to be indexed, add an additional 'Y':

    field: birthday d MDY2 Y


For more information about the date formats, please see the
documentation of the DATE class in the CSA-library.

On disk, dates are stored as longs.


12.15 Changing the record layout.

Even when the database is already in use, the need to make changes
in the record layout may occur. With the next procedure this can be
accomplished quite easily, without to the need to reenter any record
manually.

To put it in a nut shell: save your data to an ASCII file with the old
export() function and reload with the new import() function.

Or in more detail:

    a) Export with the 'old' export() function.
	This will produce an ASCII file which fully resembles the
	database.
    b) Make a new definition file or alter the old one.
    c) Generate a new Class with CSDBGEN.
    d) Compile & link.
	The last three steps are simply the procedure for creating a
	database using CSDBGEN.
    e) Use the new import() function to reload the data.
	Import the ASCII file created with step 'a'. The import()
	function is doing the actual conversion. It can do this because
	it has knowledge of both the old and the new definition file.
	The old one is on top of the ASCII file and knowledge about
	the new one is hard coded in the import() function by
	CSDBGEN.


12.16 Member functions in alphabetical order

Next is a list of the public member functions as they appear in the
generated class.
With the sole exception of open() and define(), the database needs to
be open for these functions to work properly.

void append_blank(void);
		Appends an additional record to the database. The
		record is filled with binary zeros and becomes the
		current record.
void bottom(void);
		The current record is set to the last record according
		to the active index.
void close(void);
		Closes the open files. All buffers are flushed and all
		allocated memory is released. This function is called
		automatically by the class destructor if needed.
long curr_rec(void);
		Returns the number of the current record. The first
		record is number 1.
void define(void);
		Creates a new database. Files are generated for the
		database and all the indexes.
		If a file already exists, it's overwritten.
void delet(void);
		Marks the current record for deletion. After the pack()
		function is called all the, in this way marked, records
		will be removed from the database.
int export(char *filename);
		Writes the contents of the database to an ASCII file
		'filename'. This file is meant to be read back by the
		import() function. The exported file contains a header
		which resembles the database 'definition file'. The
		function returns TRUE on succes, FALSE otherwise.
void go_to(long rec_nr);
		The record 'rec_nr' becomes the current record. This
		is independent of the active index! The record is
		directly looked up in the main database, not through
		an index. Whether or not the record is marked for
		deletion makes no difference.
int import(char *filename);
		Reads records from an ASCII file 'filename' generated
		by the export() function and appends these records to
		the database. TRUE is returned on success, FALSE
		otherwise.
int is_delet(void);
		This function returns TRUE if the current record is
		marked for deletion, FALSE otherwise.
long numrec(void);
		Returns the number of records currently in the
		database. The records marked for deletion are also
		counted.
void open(void);
		Opens the database for use. The define() function
		has to be called, that is, the database file needs to
		exist. Index files are automatically generated if they
		are missing. A runtime error is produced if something
		fails.
void order(int index_number);
		This function controls the use of indexes. The
		variable 'index_number' indicates which index has to
		become the active index. All the indexes however,
		are updated when a record is altered. In the header
		file a preprocessor constant is defined for each index.
		The name of this constant is generated by converting
		the field name to upper case and adding _INDEX.

    Example:
       An index on field:	Street
       Preprocessor constant:	STREET_INDEX
       <Class>.order(STREET_INDEX);
       will make the index on the street field the active index.

		Changing the active index does not alter the current
		record. The preprocessor constant UNSORTED can
		be used to render all the indexes inactive. The
		database will be browsed in its natural order.
void pack(void);
		Removes all the records marked for deletion. No
		temporary files are used!
void reindex(void);
		Rebuilds all the indexes of the database.
void search(void *key);
		The active index is searched for value 'key'. The
		current record becomes the first record which
		matches the search value. The function accepts a
		pointer to the search argument. The reason is that
		the indexes are not (necessarily) of the same type.
		E.g. one index may search for integers while another
		searches for strings. In this way the same search
		function can be used, no matter the field type. When
		the search argument is not exactly matched, the
		current record becomes the record with a value 'close
		to' the required key. Normally this will be the next
		'higher' value. This strategy proofs to work fine when
		searching for names etc..
int skip(int delta);
		Makes another record the current record.

    Examples:
       skip(1);     // The next record becomes the current record.
       skip(-1);    // The previous record becomes the current record.
       skip(0);     // Nothing happens.
       skip(10);    // The record 10 positions to the end becomes
		    // the current record.

		If an attempt is made to go 'before' the first record,
		record number 1 becomes the current record. Similar,
		the last record becomes the current record if an
		attempt is made to pass beyond the last record. The
		order in which the records are traversed is controlled
		by the current active index. The function returns the
		number of positions actually moved.
int to_DBASE(char *filename);
		Exports the database to a file 'filename', which can
		be read by dBASE.
		Index files (for dBASE) are NOT generated.
void top(void);
		The current record is set to the first record according
		to the active index.
void undelet(void);
		If the current record is marked for deletion, this
		function removes the marker. Otherwise nothing
		happens.


12.17 Warning

The program generator is not 'fool proof'. This means that you
should avoid using names which already are reserved C++
key words. E.g. if you try to define a field with the name
'delete' the resulting source will not compile.

12.18 Example

Let's say we want to build a database with stores a person's name and
his/hers birthday.

Step 1

First we need to construct a definition file. Next is a working example.


class:	BIRTH
record: BRecord
file:	bdays
field:	name	  s 30 T
field:	birthday  d Y4MD Y


Assumes the name of this definition file is 'birth.def'.


Step 2

From the definition file we have to generate the source for the
database. We do that by calling CSDBGEN.


c:\borlandc\csutil\test> csdbgen birth.def



This produces two output files: 'birth.cpp' and 'birth.h'.
These names are derived from the name of the definition file. Not from
the class name as one might expect from this example.


Step 3

We are now ready to start compiling. Normally, creating the database
will be an option in the main menu of the application, but because this
is a demonstration we do things differently.


#include "birth.h"

void main(void)
{

   BIRTH db;	// Declare an instance of the new BIRTH class.

   db.define(); // Create the database and its indexes.

}


Compile this together with the 'birth.cpp' file and link it.

When ran, it should create three files:
- 'bdays.dbf'   The TBASE main database file.
- 'bdays01.idx' The BTREEa index on the field name.
- 'bdays02.idx' The BTREEl index on the field birthday. Remember,
		dates are stored as longs.

If you run CSDIR in the same directory it will show something like this:



Directory C:\BORLANDC\CSUTIL\TEST\

Name		    Size      Type	Entries     Created	 Updated
--------------------------------------------------------------------------
BDAYS.DBF	     174      TBASE	      0   Nov 01 1994  Nov 01 1994
BDAYS01.IDX	     174      BTREEa	      0   Nov 01 1994  Nov 01 1994
BDAYS02.IDX	     174      BTREEl	      0   Nov 01 1994  Nov 01 1994
--------------------------------------------------------------------------
Total:		     522 bytes in   3 files.




Step 4

By now, we have created the database files and we have the class to
work with it. In other words, we are ready to write an 'application'.



#include "iostream.h"
#include "birth.h"

void main(void)
{

   BIRTH db;	    // Declare an instance of the new BIRTH class.

   db.open();	    // Open it. Because it's empty, a blank record
		    // is automatically added and becomes the current.

   db.name("Luke Skywalker");   // Modify the name.
   db.birthday("2015/07/03");   // Modify the birthday.

   db.append_blank();		// Add a new record. Becomes the current.

   db.name("Al Bundy");         // Modify the name.
   db.birthday("1945/11/30");   // Modify the birthday.

   db.reindex();		// Reindexing. For demonstration purposes.
				// Shouldn't be necessary.

   db.order(BIRTHDAY_INDEX);	// Make BIRTHDAY the active index.
   db.top();			// Go to the oldest person.
   do
   { cout<<db.name()<<endl; }	// Display his name.
   while(db.skip(1));		// Skip to the next.

   db.go_to(1); 	    // Make record 1 the current record.
			    // Index INdependent!
   db.delet();		    // Mark it for deletion.
   db.pack();		    // Remove it from the database.

   db.close();		    // Close database and indexes.
}



If you run CSDIR again afterwards, you will see something like this:



Directory C:\BORLANDC\CSUTIL\TEST\

Name		    Size      Type	Entries     Created	 Updated
--------------------------------------------------------------------------
BDAYS.DBF	    4096      TBASE	      1   Nov 01 1994  Nov 01 1994
BDAYS01.IDX	    3072      BTREEa	      1   Nov 01 1994  Nov 01 1994
BDAYS02.IDX	    3072      BTREEl	      1   Nov 01 1994  Nov 01 1994
--------------------------------------------------------------------------
Total:		   10240 bytes in   3 files.














				    Part

				   Three











      Next are some classes which can be used where the traditional
			  database will not do.
      A VRAM class is discussed which makes it possible to maintain
		       pointer structures on disk.
       Two other classes, VBASE and VBAXE, are presented which deal
		      with variable length records.
	In particular the beautiful VRAM class deserves attention!				     13 VRAM

13.1 Introduction

VRAM is without any doubt the most flexible and versatile class in this
library. Contrary to the traditional database, this one doesn't suffer from
fixed record sizes and doesn't have problems with deletions.
In other words: it isn't a database at all!

Assuming a C++ programmer has a good understanding of a 'heap', it
shouldn't take long to explain this class. In one sentence, VRAM
mimics a 'heap on a disk'.

The idea is simple: use functions like 'malloc' and 'free' to manipulate
the necessary space, just like with an ordinary heap, only this time the
heap is in fact a file. In this way the data is not lost when the program
exits while all the flexibility of a heap is still there!

In a nut shell, that's what this class does. Apart from the indispensable
functions 'define', 'open' and 'close', the class has mainly two functions:
'malloc()' and 'free()'.


13.2 Creating

	int define(char *name,U16 struclen);

This is the function needed to create a VRAM system.
Contrary to what you might have expected, it takes two parameters.
The first is as usual the file name, the second however, is the
maximum size you are planning to allocate.

This differs from the ordinary heap which simply accepts allocations of
any size right from the start. (Which also explains why the ordinary
heap allocations are so amazingly inefficient.)

In a way, the second parameter 'struclen' is a performance parameter.
If you like, you could always use the maximum, which is 32 Kb, but this
would yield a highly inefficient VRAM. The VRAM system will perform
better the more accurate 'struclen' reflects the true state of affairs.
However, performance option or not, 'struclen' is a very absolute upper
limit to what you are allowed to allocate. Any attempt to allocate more
will be answered with a runtime error.


// Example VRAM define()

#include "CSVRAM.H"

void main(void)
{

    VRAM vr;
    vr.define("VRAM.TST",614);  // Allocating at most 614 bytes.

}



The CSDIR utility recognizes VRAM files. Afterwards it will display
something like:


Directory C:\BORLANDC\TEST\VRAM\

Name		    Size      Type	Entries     Created	 Updated
--------------------------------------------------------------------------
VRAM.TST	     174      VRAM	      0   Nov 18 1994  Nov 18 1994
--------------------------------------------------------------------------
Total:		     174 bytes in   1 files.



13.3 Opening & Closing

Like all the databases classes in this library, VRAM needs to be
'opened' before it can be used and, consequently, 'closed' afterwards.

syntax: int open(char *name,U16 kb_buf);

This opens the vram file 'name' and uses 'kb_buf' Kb for buffering.

syntax: int close(void);

This closes the VRAM system. This function is also called by the class
destructor when needed.


// Example VRAM

#include "CSVRAM.H"

void main(void)
{

    VRAM vr;
    vr.define("VRAM.TST",614);  // Allocating at most 614 bytes.

    vr.open("VRAM.TST",300);    // Opens VRAM.TST using 300 Kb buffers.

    // Doing something interesting.

    vr.close(); 	    // Close VRAM system.
}




13.4 VRAM Pointers

The normal malloc() function returns a void pointer, unfortunate VRAM
cannot do that. It uses its own type of pointer: VPOI which is short for
Virtual POInter. VPOI is a simple 32 bit unsigned long, defined in
'CSVRAM.H'. VPOI also limits the size of a VRAM system. With 32 bits
4 Gb can be addressed, no more. Of course you can always use more
then one VRAM system in your application when pressed for space!

There is another important difference between VRAM and a normal
heap. VRAM distinguishes between reading and writing. The buffer
system used, cannot tell whether you are making changes. Therefore,
the programmer need to supply that information by calling different
functions for reading and writing.

Reading:    char *R(VPOI p);
Writing:    char *W(VPOI p);


// Example

#include "CSVRAM.H"

void main(void)
{

    VRAM vr;		    // A VRAM system.
    VPOI vp;		    // A VRAM pointer.
    char *cp;		    // A normal character pointer.

    vr.define("VRAM.TST",614);  // Initially create it.

    vr.open("VRAM.TST",50); // Opening with 50 kB buffers.

    vp=vr.malloc(20);	    // Allocate 20 bytes from the virtual heap.

    cp=vr.W(vp);	    // Obtaining a character pointer to
			    // the allocated space. We are planning to
			    // write, so the 'W' function is used.

    strcpy(cp,"Some Data"); // Write data into it.

    vr.close(); 	    // Close the VRAM system.
			    // "Some Data" is now on disk!

}



From the above example it becomes clear how the VPOI pointers can
be used. The method is simple: convert them into normal pointers and
apply standard C++ programming technique.

Only the last 2 converted VPOI pointers are guaranteed to be
valid. VRAM has a limited number of buffers, so you cannot
expect all data to be in ram forever.
Every time you convert a VPOI pointer into a character pointer by using
the W() or the R() function, VRAM calculates the corresponding position
in the file and loads the required page in ram. The pointer returned
points directly into this page. Because only the last two pages are
guaranteed to be in ram under all circumstances, the third time you
convert a VPOI pointer, it can overwrite a previously loaded page.

Because at least two pointers are valid, you can copy data from one
VRAM position to another without using temporary storage.

With the W() function, the loaded page is marked 'dirty' which makes
sure it's written back to disk when the page is removed from the buffer
system. This is not so for the R() function. In that case the page is
simply discarded.


// Example, copying between two VPOI pointers.

#include "CSVRAM.H"
void main(void)
{

    VRAM vr;
    VPOI vp1,vp2;

    vr.open("VRAM.TST",50); // Opening with 50 kB buffers.

    strcpy(vr.W(vp1=vr.malloc(20)),"Some Data");    // Allocate and fill
					    // one VPOI.

    vp2=vr.malloc(100); 		    // Allocate a second.

    memcpy(vr.W(vp2),vr.R(vp1),20);	    // Copy!

    vr.close(); 	// Close the VRAM system.
}



13.5 Fragmentation

Just as with an ordinary heap, VRAM can suffer from fragmentation.
The normal heap can become prematurely exhausted because of
fragmentation while for the VRAM system it only means the file
becomes larger then strictly necessary.
On the other hand: the normal heap gets a fresh start every time the
program is run while the VRAM files may be in use for years.

Therefore a defrag() function is available. If you decide to use it, it is
best to use it regularly. It mainly does three things:
a)  Joining free space wherever possible.
    This is not done during normal operation because it may involve
    additional IO.
b)  Sorting the empty-data-chains.
    When space is needed, its taken from the beginning of a empty-
    chain. After sorting the chains, the empty blocks at the beginning
    of the file will also be at the beginning of the chain. Eventually this
    leads to pages at the end becoming completely free and pages at
    the beginning (almost) full.
c)  Empty pages above the highest used location are stripped from the
    file.

The defrag() function links in a lot of code, it uses an entire
btree and a temporary file. In a way this makes the defrag()
function 'bigger' then the rest of the VRAM class combined!

13.6 Root

Under some circumstances you may need a 'starting point' in the
VRAM.
Example:
    Let's say you are writing some flowcharting program and you have
    decided that VRAM is a great help in storing and manipulating a
    flowchart. The flowchart probably consists of several independent
    parts pointered together. Once in it, each part can be reached by
    the VPOI's stored in the data structure. This leaves you with just
    one problem: where does the flowchart start?
    It takes just one VPOI to store that location and it would be a
    shame if you needed an additional configuration file for that.

Therefore two very simple functions are implemented to store and
retrieve a 'special' VPOI.

void  root(VPOI p);	Stores VPOI 'p'.
VPOI root(void);	Obtains the VPOI stored with the previous
			function.

These functions just manipulate this single VPOI. They have absolutely
no effect on the rest of the VRAM system.

13.7 Functions in Alphabetical order.

Prototypes are in 'csvram.h'. With the exception of the define(). open()
and zap() functions, the class needs to be opened for the functions to
work.

U16 alloc(VPOI p);
U16 alloc(void *p);
		Returns the number of allocated bytes at a certain
		location. The pointer may be either a VPOI pointer or
		a normal pointer to the same location.
int close(void);    Closes the VRAM system. Returns TRUE on
		    success, FALSE otherwise.
int define(char *name,U16 struclen);
		Creates the VRAM system 'name' with 'struclen'
		being the maximum size of any allocation. Returns
		TRUE on success, FALSE otherwise.
int defrag(void);   Defragments the virtual heap. Returns TRUE on
		    success, FALSE otherwise.
int empty(void);    Makes the VRAM system empty. The class remains
		    open but all allocations will be undone. Returns
		    TRUE on success, FALSE otherwise.
U32 number(void);
		Returns the number of allocations currently done.
		This is the number of malloc()'s minus the number of
		free()'s.
int open(char *name,S16 kb_buf);
		Opens VRAM 'name' using 'kb_buf' Kb ram for
		buffering. Returns TRUE on success, FALSE
		otherwise.
char *R(VPOI p);
		Converts a VPOI pointer into a character pointer. It is
		assumed no modifications are going to take place.
void  root(VPOI p);
		Stores VPOI 'p'.
VPOI root(void);    Obtains the VPOI stored with the previous functions.
int save(void); Makes a safety backup. The VRAM system remains
		open. All the buffers are flushed and the header page
		is updated. Returns TRUE on success, FALSE
		otherwise.
void free(VPOI p);
		Frees the VPOI p.
VPOI malloc(U16  size);
		Allocates 'size' amount of bytes from the virtual heap.
		The corresponding VPOI is returned.
char *W(VPOI p);
		Converts a VPOI pointer into a character pointer. It is
		assumed modifications are going to take place.
int zap(void);	Closes the VRAM system when needed and restores
		all class defaults. Returns TRUE on success, FALSE
		otherwise.

				   14 VBASE


14.1 Introduction

The use and purpose of the VBASE class are much similar to the
TBASE class. There is however, one huge difference, VBASE supports
variable length records! The 'V' in VBASE stands for 'variable'.

Compared with TBASE, the differences in the public member functions
are minimal. The append() function now takes an additional parameter
indicating the length of the record. The same goes for the write_rec()
function. Apart from the 'normal' read_rec() function there is now an
additional read_rec() which returns the length of the obtained record.

VBASE is a 'stand alone' class. It has nothing to do with the
databases produced by CSDBGEN.



14.2 Using VBASE.

Using VBASE is very straightforward.

- Initially create the VBASE system by calling define().
- Open it through a call to open().
- Read, write and append records.
- Close VBASE by calling close().

That's all!


// Example

#include "csvbase.h"

void main(void)
{

    VBASE vb;

    vb.relocate_when_shrunk(TRUE);  // Move the record to a better
				    // fitting position when shrunk.
    vb.define("VBASE.dbf",1230);    // Maximum record length 1230 bytes.

    vb.open("VBASE.dbf", 200);      // Open with 200 Kb buffers.

    char *s="Some chunk of data. ";

    vb.append_rec(s,strlen(s)+1);   // Append a record. Notice the length
				    // parameter which is not needed with
				    // TBASE.
    char d[200];
    vb.read_rec(1,d);		    // Read record 1 into array 'd'.

    strcpy(d,"New Data");

    vb.write_rec(1,d,strlen(d)+1);  // Overwrite record 1 with a new
				// block of data. This does not have
				// to have the same length!

    vb.close(); 		// Ready. Close VBASE. Also called by
				// the class destructor if needed.

}


For more information, please read the documentation on the TBASE
class.


14.3 Relocating records

When an existing record is overwritten with a new bigger record, it no
longer fits in its original slot, which means the record has to be
relocated. This is not necessarily so when the record shrinks. In that
case you have the choice between relocating, which saves disk space
but is relatively slow or leaving the record where it is and waste some
disk space.

The function 'relocate_when_shrunk()' is there to choose between these
two strategies. It has to be called before 'define()'.
Calling 'relocate_when_shrunk(TRUE);' will relocate a record when it
becomes smaller. 'Relocate_when_shrunk(FALSE);' will leave the
records in place when possible.
The default is set to: relocate_when_shrunk(TRUE).

The function has to be called before 'define()' and its setting
cannot be altered afterwards.



14.4 Limitations.

VBASE was designed for databases up to around a million records.
This is not a 'hard' limit, its possible to add many more records but
under some unfavourable conditions memory utilization can get out of
control. The way the class uses the available ram is controlled by the
open() function. The worst thing you can do is open an empty VBASE
and append 3 million records in one go. In this way the class cannot
adjust the way its using memory. If the records are not appended all at
once but with several close/open sequences in between, VBASE can
easily store 16 million records.

So:
- Under worst case conditions 1 million records.
- Under favourable conditions 16 million records.
- Avoid more then 16 million records.

The above limitations stem from ram utilization. For those drowning in
memory, there are also software limitations:
- maximum file size 4 Gb.
- 4 billion records.


Because there are so many 'buts' and 'ifs', there is another class
VBAXE, discussed in the next chapter, to deal with the larger
databases.

As a rule of thumb, use VBASE for databases up to 1 million
records and VBAXE for more then 1 million records.

14.5 Functions in alphabetical order.

The function prototypes are in csvbase.h.


U32 append_rec(void *data,U16 len);
		Append a record to the database. 'data' is a pointer
		to the data and 'len' is the number of bytes data. The
		function returns the number of the newly created
		record.
int close(void);
		Closes the database. All buffers are flushed and all
		allocated memory is freed. TRUE is returned on
		success, FALSE otherwise.
int define(char *name,U16 struclen);
		Creates a new database. 'name' is the name of the
		file and 'struclen' is the maximum length of a record.
		Do not make 'struclen' unnecessary large because its
		value is important for space efficiency. The maximum
		value of struclen is 32767. TRUE is returned on
		success, FALSE otherwise.
void delet(U32 record);
		Marks record 'record' for deletion. Only the 'delete bit'
		is set. The pack() function needs to be called to
		actually remove the record from the file.
void empty(void);
		Removes all records from the database. Upon return
		the database will contain zero records but will still be
		'open'.
int is_delet(U32 record);
		Returns TRUE if record 'record' is marked for
		deletion. FALSE otherwise.
U32 numvrec(void);
		Returns the number of records currently in the
		database.
int open(char *name,U16 kb_buf);
		Opens database 'name', using 'kb_buf' Kb ram for
		buffering. Returns TRUE on success, FALSE
		otherwise.
int pack(void); Removes all records marked for deletion. A
		temporary file is used. TRUE is returned on success,
		FALSE otherwise.
void read_rec(U32 rec,void *ptr,U16  &length);
		Reads record 'rec' and copies it into the buffer 'ptr' is
		pointing at. The variable 'length' is set to the length of
		the retrieved record.
void read_rec(U32 pos,U16  maxlen,void *ptr,U16  &length);
		The same as the precious function but with an
		additional parameter 'maxlen' specifying the
		maximum number of bytes that can be copied into
		the buffer 'ptr'. If the record proofs to be longer then
		'maxlen', only 'maxlen' bytes will be copied to 'ptr'.
U16 rec_len(U32 rec);
		Returns the length of record 'rec'.
void relocate_when_shrunk(int TrueOrFalse);
		When called with 'TrueOrFalse' set to TRUE, records
		will be relocated when shrunk. When called with
		FALSE the records will stay at the same place. The
		function has to be called before define(). For more
		information, please see the paragraph about this
		topic.
int save(void); As a precaution measure, all 'dirty' buffers are written
		to disk and the header page is updated. The
		database remains open. Returns TRUE on success
		and FALSE otherwise.
void undelet(U32 rec);
		Removes the 'delete' marking from record 'rec'.
void write_rec(U32 rec,void *data,U16 len);
		Overwrites the existing record 'rec'. 'len' bytes are
		copied from 'data'. Afterwards the record will be of
		length 'len'.

				   15 VBAXE

15.1 Introduction

As explained in the previous chapter, VBAXE is similar to VBASE but is
intended for larger databases. That is, more then 1 million records.

The public member functions of the classes are 100% identical. The
inner workings however are completely different. VBAXE uses two files
for a database where as VBASE uses only one. VBAXE is build from
two other classes namely TBASE and VRAM.

Building a class for variable length records is not easy, but writing one
that can store millions of records, is fast, uses little ram, doesn't use
unnecessary disk space and still stores everything in one file is next to
impossible.
So, rather then coming up with something slow & clumsy, VBAXE gives
up on storing everything in one file which seems to be the lesser evil.


15.2 Working.

The working of VBAXE is very simple. It allocates the necessary space
from VRAM and stores the VRAM pointer together with the length in a
TBASE record.
E.g. to obtain record 714 it starts with retrieving record 714 from
TBASE. Because TBASE uses fixed size records, the position of record
714 can easily be calculated. Once this record is obtained, the VRAM
pointer to the data of record 714 is known. From this pointer the
position in the VRAM file can again be easily calculated. If nothing is in
the buffers, it takes two IO's to obtain the data, but at least no
searching is done. The positions in the files are always known through
simple arithmetic. (This also holds for the VBASE class.)


15.3 Files

As explained above there are two files to every VBAXE database. The
TBASE part stores it's data in a file with extension '.vbi'. The VRAM
part uses extension '.vbd'. No matter what names are used in the
define() or the open() functions, these are the extensions used.
The CSDIR utility recognizes these files and will display the TBASE
class as VBASEi and VRAM as VBASEd. In this way its immediately
apparent they belong to the same database.


// Example

#include "csvbaxe.h"

void main(void)
{
    char buf[1000];

    VBAXE vb;
    vb.define("demo",390);      // Max record length 390 bytes.

    vb.open("demo",200);        // 200 Kb buffers.

    for(int i=1;i<=100;i++)
    {
	vb.append_rec(buf,1+random(390));   // Append 100 records
					// with random length
					// and random contents.
    }

    vb.close(); 	    // Close database.
}


Afterwards CSDIR will display something like:



Directory C:\BORLANDC\DEMO

Name		Size	  Type	Entries     Created  Updated
--------------------------------------------------------------------------
DEMO.VBI	    4096      VBASEi	    100   Dec 14 1994  Dec 14 1994
DEMO.VBD	   26624      VBASEd	    100   Dec 14 1994  Dec 14 1994
--------------------------------------------------------------------------
Total:	       30720 bytes in	2 files.



However, CSINFO still says DEMO.vbi is a TBASE file and DEMO.vbd
a VRAM file.


15.4 Prototypes.

The class defintion and it's function prototypes are in "CSVBAXE.H".








				    Part

				    Four






	   Part Four will present some command-line utilities.
	 Most noticeable CSDIR, which gives a quick survey of the
		   databases in the current directory.
	  It also discusses the demonstration application CSADD.
	      CSADD is a DOS application to store addresses.				       16 CSDIR


CSDIR is an on-line utility similar to the well-known MS-DOS dir.
It's purpose is to list the CS-databases. By default it ignores all other
files.

	SYNTAX: csdir [filename] [/A] [/?]

	filename: the file(s) to be listed. Wildcards allowed.
	/A  List all files.
	/?  Display help.

Example of its output:


c:\bin\adres>csdir


Directory C:\BIN\ADRES\

Name		    Size      Type	Entries     Created	 Updated
--------------------------------------------------------------------------
CSADR.DBF	   98382      TBASE	    298   Sep 20 1994  Oct 31 1994
CSADR01.IDX	   40960      BTREEa	    403   Oct 29 1994  Oct 31 1994
CSADR02.IDX	   10752      BTREEa	    104   Oct 29 1994  Oct 31 1994
CSADR03.IDX	    4608      BTREEl	     28   Oct 29 1994  Oct 31 1994
CSADR04.IDX	    5120      BTREEa	     22   Oct 29 1994  Oct 31 1994
--------------------------------------------------------------------------
Total:		  159822 bytes in   5 files.



As can be seen from this example, CSDIR displays:
    - the name of the class involved.
    - the number of entries in the database.
    - in case of a btree, the number of different keys. If the same key
	is entered twice, it is counted as one entry.
    - date of creation.
    - date of last update.

Example of the /a option.


c:\bin\adres>csdir /a


Directory C:\BIN\ADRES\

Name		    Size      Type	Entries     Created	 Updated
--------------------------------------------------------------------------
ADRES.EXE	  137872       DOS			       Oct 29 1994
CSDEMIO.DEF	     277       DOS			       Apr 17 1994
BACKUP.TXT	   34478       DOS			       Oct 29 1994
CSADR.DBF	   98382      TBASE	    298   Sep 20 1994  Oct 31 1994
CSADR01.IDX	   40960      BTREEa	    403   Oct 29 1994  Oct 31 1994
CSADR02.IDX	   10752      BTREEa	    104   Oct 29 1994  Oct 31 1994
CSADR03.IDX	    4608      BTREEl	     28   Oct 29 1994  Oct 31 1994
CSADR04.IDX	    5120      BTREEa	     22   Oct 29 1994  Oct 31 1994
ERROR.ERR	   12964       DOS			       Oct 27 1994
--------------------------------------------------------------------------
Database files:   159822 bytes in   5 files.
Other files:	  185591 bytes in   4 files.
		-------- +	  --- +
Total:		  345413 bytes in   9 files.




Another example:


c:\bin\adres>csdir cs*.* /a


Directory C:\BIN\ADRES\

Name		    Size      Type	Entries     Created	 Updated
--------------------------------------------------------------------------
CSDEMIO.DEF	     277       DOS			       Apr 17 1994
CSADR.DBF	   98382      TBASE	    298   Sep 20 1994  Oct 31 1994
CSADR01.IDX	   40960      BTREEa	    403   Oct 29 1994  Oct 31 1994
CSADR02.IDX	   10752      BTREEa	    104   Oct 29 1994  Oct 31 1994
CSADR03.IDX	    4608      BTREEl	     28   Oct 29 1994  Oct 31 1994
CSADR04.IDX	    5120      BTREEa	     22   Oct 29 1994  Oct 31 1994
--------------------------------------------------------------------------
Database files:   159822 bytes in   5 files.
Other files:	     277 bytes in   1 files.
		-------- +	  --- +
Total:		  160099 bytes in   6 files.





				  17 CSINFO


CSINFO is an on-line DOS utility to display information about a
particular database.
It only recognizes the databases made with the CSDB-library.

An example of its output:



c:\adres>csinfo csadr01.idx


  Information about database: csadr01.idx.

   Type..................:  BTREEa
   Version...............:  1.0.a
   Class compiled at.....:  Sep 05 1994, 04:28:24
   With..................:  Borland C++ 3.1

 NOTE: The above information refers to the version of the
       class used during the CREATION of the database file.

   Btree created at......:  September 20 1994, 10:02:11,47
   Btree last updated at.:  September 26 1994, 23:25:19,96
   Multiple keys allowed.:  YES
   Number of keys........:  622
   Number of blocks......:  111
   Block size............:  511 bytes
   Key size..............:  41 bytes
   Data size.............:  4 bytes
   Data degree...........:  10
   Index degree..........:  10
   Number of levels......:  4



				  18 CSERROR


Normally all the errors are read from a file. This is the file 'error.err'. It
has to be in the current working directory or it cannot be found.

The advantage in using a runtime error file is of course the smaller
executable that results from leaving out all the possible error
messages.
The error file is not kept open all the time. For opening a file, some
dynamic memory allocations have to be done. This can lead to
problems when the error message that has to be displayed results from
an 'out of memory' condition.
(It needs memory to say 'there is no more memory'.)

To overcome this and other problems, CSERROR can be used.
It generates C source that makes the runtime error file redundant.


Example:
c:\borlandc>cserror error.err


This will produce a file 'error.cpp' in the current directory. Compile this
and link it in with the rest of your application, but before the libraries. In
this way the 'error.obj' will replace the 'csmess_read()' function which is
in the library.


// Example of how the resulting 'error.cpp' file could look:
// Many errors are left out.


#include "csmess.h"

char *_csa_error[]=
     {
       "Error 9370: TBASE: %s Can't write report file %s. Disk full?",
       "Error 9390: TBASE: %s Out of memory during pack().",
       "Fatal Error 9545: PAGE: %s Header_2_data(): can't perform fseek.",
       "Fatal Error 9550: PAGE: %s Write_header: can't perform fwrite.",
       "Fatal Error 9555: PAGE: %s Header_2_data(): can't perform fread.",
       "Fatal Error 9560: PAGE: %s Can't open file during definition.",
       "Error 9562: PAGE: %s Can't open report file %s.",
       "TheEnd"  //THIS HAS TO BE THE LAST LINE!!
     };

/////////////////////////////////////////////////////////////////////

char *csmess_read(long error)
{
   char tmp[25];
   ltoa(error,tmp,10);
   char **p=_csa_error;
   for(;;)
   {
      if(strstr(*p,tmp)) return *p;
      if(!strcmp(*p,"TheEnd")) return NULL;
      p++;
   }



Notice the 'TheEnd' line, which was not in the original
'error.err' file. Never remove that line!











				   19 CSADD


19.1 Source

This is a demonstration database for storing addresses.
It gives an adequate impression of what this library was designed for.
No complex client/server approach but strait forward functions to read
and write records.
The user interface is written with the aid of the CSA-library.

The source consists of two files: 'csadd.cpp'  for the user-interface and
'csaddio.cpp' for the database.
    - 'csaddio.cpp' is generated by the CSDBGEN utility with the file
	'csaddio.def' as input.
    - 'csadd.cpp' is 'hand coded'. The file is about 500 lines.


19.2 Openings screen

This is how it is supposed to look:


 eXit  Insert  Delete  Edit  Sort order  Output  Utilities  Setup	Help=F1
Ŀ
       Address DataBase ͻ       
      					  Updated:  09/29/94          
      							              
      	 Name:			       City:		              
      	 Roberta Grundburg	       Kopenhagen	              
      							              
      	 Address:		       Telephone:	              
      	 23 Grondlsy		       0978-234-56756	              
      							              
      	 Zip code:		       Country: 	              
      	 TY 2347		       Denmark		              
      							              
      	 Birthday:		       Relation:	              
      	 12/04/1977		       45		              
      							              
      	 Info:						              
      							              
      ͼ       
  Ŀ   
  	Command: rob				     Record 243/243	      
     




19.3 Features

This simple demonstration database has already some interesting
features:
    - Incremental search. That is, it 'zero's in' on the name you are
	looking for with every key entered. Normally it finds the
	required record after only two or three characters.
    - Exporting to a dBASE compatible file. The files used by the
	application are not in the dBASE format, but they can be
	exported to a dBASE compatible file.
    - Indexing also on substrings. Contrary to the traditional database,
	this one is capable of locating a record by searching for a
	substring of the key field, rather then the entire field.













				    Part

				    Five







     Part Five discusses the classes and functions implemented in the
			      CSA-library.
     They have nothing to do with databases so you can use or ignore
			      them at will.
	    However, two chapters may require some attention.
     Alloc-logging which deals with heap corruption and memory leaks.
     The HEAP class for efficiently allocating large numbers of small
				  blocks.



				  20 CSTOOLS


20.1 Introduction

A collection of odds & ends, merely intended to support the other
classes, but if you see something to your liking, please feel free to use
it.


int add_path(char *filen,char *path);
		Adds path 'path' to filename 'filen'. Afterwards 'filen'
		contains the new name. It returns TRUE if successful,
		FALSE otherwise.
void box(int row,int col,int h,int w,int border,int color);
		Draws a box on the screen. (Non graphical.)
		row:	top row
		col:	    left column
		h:	height
		w:	width
		border: type of border
			BORDER_NONE, BORDER_SPACE
			BORDER_SINGLE, BORDER_DOUBLE
		color:	text ATTRIBUTE of the border line.
int  csrand(long int);
long csrand(long amount);
		Returns a VERY random number in the range
		0..(amount-1), including both 0 and (amount-1).
int cstmpname(char *name);
		Generates the name of a non-existing file in the
		'temp' directory. It first searches for the environment
		variable 'TMP' and if not found for 'TEMP'. A filename
		is generated which does not already exist in this
		directory. The function has to be called with a
		parameter 'name' pointing to a buffer large enough to
		hold the complete drive, path and filename. If non of
		the evironment variables exist, a filename for the
		current directory is produced. It only generates a
		filename, no file is actually created. The function
		returns TRUE if a unique filename was found, FALSE
		otherwise.
int disk(char *s);
		Sets the current drive and path as indicated by string
		's'. If 's' is the empty string, 's' is set to the current
		drive and path! It returns TRUE if successful, FALSE
		otherwise.
void empty_kb(void);
		Empties the keyboard buffer.
int file_exist(char *fnaam);
		Returns TRUE if file 'fnaam' exists.
char *file_ext(char *name,char *ext);
		Adds an extension to filename 'name'. It returns a
		pointer to an internal buffer which contains the new
		name. If 'name' already has an extension, it is
		overwritten. The string 'name' itself is not changed!
long filesize(char *name);
		Returns the size of file 'name'.
void filter_string(char *source,char *allowed);
		All the characters in 'source' which are not in
		'allowed' are removed from 'source'.
void gotoyx(int y,int x);
		Same as Borlands gotoxy(x,y) but with reversed
		parameters.
int is_color(void);
		Returns TRUE if you are using a color screen.
		FALSE otherwise.
void lower_upper(char *ptr);
		Converts the entire string to upper case.
long lrandom(long amount);
		Returns a long random number in the range
		0..(amount-1), including both 0 and (amount-1).
int make_color(int fgc,int bgc);
		Returns the text attribute corresponding with the Fore
		Ground Color (fgc) and the Back Ground Color (bgc).
size_t next_prime(size_t pri);
		Calculates next higher prime number.
char *notabs(char *s);
		Replaces every occurrence of a tab character in 's'
		with a single space. That is: tabs are not expanded,
		but simply removed.
char *remove_space(char *s);
		Removes ALL the blanks from the string 's'. It returns
		character pointer 's'.
unsigned int  sqrti(unsigned int n);
unsigned long sqrtl(unsigned long n);
		Calculates the sqrt from n WITHOUT USING
		FLOATING POINT arithmetic.
void str_split(char *source,char ch,char *first,char *last);
		Split 'string' source at the first occurrence of character
		'ch'. Ch is included in neither the 'first' nor the 'last'
		string.
void str_strip(char *source,char *remove);
		Characters in 'source' which are also in 'remove' are
		removed from 'source'.
int str_equal(char *s1, char *s2);
		Returns TRUE if 's1' is equal to 's2', discriminating
		between upper and lower case.
void str_left(char *source,char *dest,int len);
		Copies at most 'len' number of characters from the
		left of 'source' to 'dest'.
char *string_replace_ones(char *source,char *d,char *r);
		Replaces the first occurrence of 'd' in 'source' with 'r'.
int string_replace(char *s,char *d,char *r);
		Replaces every occurrence of 'd' in 'source' with 'r'. It
		returns an integer number indicating the number of
		times a substitution was made.
long time_stamp(void);
		Returns a higher long number on each successive
		call, starting with zero again when MAXLONG is
		reached.
void trim_string(char *s);
		Removes heading and trailing blanks from string 's'.
void wait(long msec);
		Waits 'msec' milliseconds.
void waitkb(long msec);
		Waits 'msec' milliseconds or until the next keyboard
		hit.

				  21 CSKEYS


Almost all the input for the library functions is done through the cskey()
function.

Syntax:
    int cskey(void);

The return value can be one of the following: ( defined in CSKEYS.H )


     CTRL_A    KEY_A	KEY_a	  ALT_A     DELETE  CURSOR_UP
     CTRL_B    KEY_B	KEY_b	  ALT_B     END     CURSOR_DOWN
     CTRL_C    KEY_C	KEY_c	  ALT_C     HOME    CURSOR_RIGHT
     CTRL_D    KEY_D	KEY_d	  ALT_D     PAGE_UP CURSOR_LEFT
     CTRL_E    KEY_E	KEY_e	  ALT_E     PAGE_DOWN
     CTRL_F    KEY_F	KEY_f	  ALT_F     INSERT
     CTRL_G    KEY_G	KEY_g	  ALT_G     BACKSPACE
     CTRL_H    KEY_H	KEY_h	  ALT_H     TAB
     CTRL_I    KEY_I	KEY_i	  ALT_I     SHIFT_TAB
     CTRL_J    KEY_J	KEY_j	  ALT_J     ENTER
     CTRL_K    KEY_K	KEY_k	  ALT_K     ESC
     CTRL_L    KEY_L	KEY_l	  ALT_L     SPACE
     CTRL_M    KEY_M	KEY_m	  ALT_M
     CTRL_N    KEY_N	KEY_n	  ALT_N     CTRL_DELETE
     CTRL_O    KEY_O	KEY_o	  ALT_O     CTRL_HOME
     CTRL_P    KEY_P	KEY_p	  ALT_P     CTRL_CURSOR_UP
     CTRL_Q    KEY_Q	KEY_q	  ALT_Q     CTRL_CURSOR_DOWN
     CTRL_R    KEY_R	KEY_r	  ALT_R     CTRL_CURSOR_RIGHT
     CTRL_S    KEY_S	KEY_s	  ALT_S     CTRL_CURSOR_LEFT
     CTRL_T    KEY_T	KEY_t	  ALT_T     CTRL_PAGE_UP
     CTRL_U    KEY_U	KEY_u	  ALT_U     CTRL_PAGE_DOWN
     CTRL_V    KEY_V	KEY_v	  ALT_V     CTRL_END
     CTRL_W    KEY_W	KEY_w	  ALT_W
     CTRL_X    KEY_X	KEY_x	  ALT_X
     CTRL_Y    KEY_Y	KEY_y	  ALT_Y
     CTRL_Z    KEY_Z	KEY_z	  ALT_Z

     F1     SHIFT_F1	CTRL_F1   ALT_F1    KEY_1
     F2     SHIFT_F2	CTRL_F2   ALT_F2    KEY_2
     F3     SHIFT_F3	CTRL_F3   ALT_F3    KEY_3
     F4     SHIFT_F4	CTRL_F4   ALT_F4    KEY_4
     F5     SHIFT_F5	CTRL_F5   ALT_F5    KEY_5
     F6     SHIFT_F6	CTRL_F6   ALT_F6    KEY_6
     F7     SHIFT_F7	CTRL_F7   ALT_F7    KEY_7
     F8     SHIFT_F8	CTRL_F8   ALT_F8    KEY_8
     F9     SHIFT_F9	CTRL_F9   ALT_F9    KEY_9
     F10    SHIFT_F10	CTRL_F10  ALT_F10   KEY_0
     F11    SHIFT_F11	CTRL_F11  ALT_F11
     F12    SHIFT_F12	CTRL_F12  ALT_F12

The predefined values for the 'normal' keys like 'A', 'a' or '1'
are the same as the ASCII values. This means you are not
forced the type things like

	    if( KEY_A==cskey() ) ....

	but can also use:

	    if( 'A'==cskey() )  ......


21.1 CSKEYS.exe

There is also a simple utility to test the return value of the cskeys()
function.

This is cskeys.

With this it becomes very easy to make your own additions to the
predefined keys in 'cskeys.h'.

				   22 HEAP


22.1 Purpose

To avoid having to allocate many small blocks, a special HEAP class is
implemented. The idea is to do allocations in chunks of about 2Kb and
take the small amounts from that when needed.

This approach has considerable advantages.
    - You can release all allocated memory with just one function call
	instead of freeing many small blocks separately.
    - It is a lot faster because normal heap operations are relatively
	slow.
    - Heap efficiency is also improved. It is much easier for the heap to
	deal with relatively few allocations of about 2Kb then it is to
	deal with numerous small allocations.
    - It can save valuable memory. There is considerable overhead
	involved in using the heap. Apart from what you need, several
	additional bytes are used to 'pointer' the allocated blocks
	together. In addition, allocations are done in multiples of 16
	bytes. This can lead to a situation where you need only 13
	bytes while 32 bytes are used!


22.2 When to use it?

The HEAP class is particularly useful when dealing with pointer
structures in ram. Pointer structures are small, all of the same size and
an application will probably use a lot of them.
The BUFFER class described earlier in this documentation also uses
the HEAP class. This means you can use it without enlarging your
application.
The HEAP class assumes allocations of a fixed size. This limits its
usefulness but improves efficiency. It is very well possible to use more
then one instance of the HEAP class in an application. It is feasible to
use a different HEAP for every size of allocation needed.
The class was designed with small allocations in mind, something
below 50 bytes. It is doubtful whether the HEAP class still makes sense
for allocations above 100 bytes.

Summarizing:
- Allocations have to be of a fixed size.
- Allocations have to be small, below 50 bytes.
- Many allocations of this type are going to take place.


22.3 Using HEAP.

Using the HEAP class starts of with an initialization, stating the size of
the allocations. Afterwards the class has to be 'opened'. From there on
allocations can be made, and blocks can be free-ed again. When the
work is done, the close or the zap function can be called to free all
allocated memory.



// Example

#include "csheap.h"

void main(void)
{
    typedef struct
    {
	void *next;
	void *prev;
	int  number;
    } pStruct;		// A typical pointer structure.

    HEAP heap;		// HEAP class instance.

    heap.init(sizeof(pStruct)); // Initialize it for the size of the
			    // pointer structure.

    heap.open();	    // Open the class so it can be used.

    pStruct *p,*q;

    p=(pStruct *)heap.malloc(); // Allocation.
    q=(pStruct *)heap.malloc(); // Allocation.

    p.next=q.prev=NULL; // Doing something.

    // Doing much more.

    heap.free(q);	    // Freeing q.

    heap.close();	    // Finally finished.
			    // Freeing all allocated memory.

}




22.4 Functions in alphabetical order.

The function prototypes are in CSHEAP.H.

void close(void);
		Closes the class. All allocated memory is freed. The
		initialisation parameters are retained, which makes it
		possible to reopen the class without calling init(). This
		function is also called by the class destructor.
void empty(void );
		Frees all allocated memory, but the class remains
		open.
void init(U16 alloc_size,U16 page_size=2048);
		Initializes the class. 'Alloc_size' is the size of the
		allocations needed. 'Page_size' is the size of the
		chunks which are going to be allocated from the
		heap. This parameter has a default value of 2048
		bytes.
int open(void); Opens the class. The 'init()' function has to be called
		first. The function returns TRUE on success and
		FALSE otherwise.
void free(void *p);
		Frees the previously allocated block 'p' is pointing at.
void *malloc(void);
		Allocates a block and returns a pointer to it. If no
		memory is available, a NULL pointer is returned.
void zap(void); Frees all allocated memory and closes the class. The
		initialization parameters set by the init() function are
		reset. This means the class has to be initialized again
		before it can be reopened.



			       23 Alloc-Logging

23.1 Introduction

Dynamic memory allocations can create problems which are difficult to
trace. Therefore, this library contains a set of functions which can be
used to replace the normal malloc() and free() functions. The
replacements can be made to write a record to a log. This log can be
used later to check for memory leaks.

The replacements also test for things like freeing a NULL pointer or a
malloc which returns NULL. In the DOS version, Borlands 'heapcheck'
functions are called to test for heap integrity.

Replacements are preprocessor commands which can be switched on
and off with the preprocessor variable CS_DEBUG.
If CS_DEBUG is not defined, the normal functions are called.

23.2 Replacements

Replacements are available for the following functions:

   Function:		    Replacement:
    malloc		    csmalloc
    calloc		    cscalloc
    realloc		    csrealloc
    free		    csfree
    farmalloc		    csfarmalloc
    farcalloc		    csfarcalloc
    farrealloc		    csfarrealloc
    farfree		    csfarfree

The use of the replacements is fully equivalent to the original.

The allocations in the CS-libraries are always done by calling the
replacements. The production version of the libraries was compiled
without CS_DEBUG being defined, so the normal functions are used
and are called without any additional overhead. When the debug
version was compiled, CS_DEBUG was defined and as a result all the
allocations done by the library functions can be logged!


23.3 Logging

Logging of allocations can be switched on and off through the use of
two functions.

void alloc_logging(int TrueFalse);
		After a call to alloc_logging(TRUE) all the allocations
		are logged in the ASCII file 'malloc.log'. Calling
		alloc_logging(FALSE) switches the logging off.
void alloc_logging(int TrueFalse,char *name);
		This is basically the same as the previous function
		but has the additional option of specifying the name
		of the log file.

The next example displays a part of an allocation log.



 4E79:0004	   file csedst30.cpp	line 8: malloc()    8 bytes
 4E7A:0004	   file csedst30.cpp	line 8: malloc()    8 bytes
 4E78:0004	   file csedst30.cpp  line 23: free()
 4E78:0004	   file csedst30.cpp	line 8: malloc()    9 bytes
 4E79:0004	   file csedst30.cpp  line 23: free()
 4E7B:0004	   file csedst30.cpp	line 8: malloc()    22 bytes
 4E7B:0004	   file csedst28.cpp  line 12: realloc free()
 4E7B:0004	   file csedst28.cpp  line 12: realloc malloc()
 4E7A:0004	   file csedstr.cpp  line 15: free()
 4E7B:0004	   file csedstr.cpp  line 15: free()




As can be seen, the first column displays the pointer involved, the
second and third display the file and the line where the call was made.
When an allocation is concerned its size is also displayed. Reallocs
appear as two lines.


23.4 Memory Leaks.

With a log like this it is easy to check for memory leaks. In fact, a
command-line utility is supplied to check for that. It is called
CSMALLOC. Only one parameter needs to be supplied: the name of
the allocation log.

Example

c:\test>CSMALLOC malloc.log




If it encounters a malloc which is not matched by a free, it displays the
pointer involved.
Like:



 UNMATCHED address: 29CC:0004



If all malloc's are matched by a free it says something like this:



  NO ERRORS encountered!!


  Number of addresses: 23
  Lowest address:  29CC:0004
  Highest address: 2EAE:0004



If malloc logging is kept on for longer periods, the log file can become
extremely large. However, this poses no problem for CSMALLOC.

If you are planning to use this method to detect memory
leaks, it is essential to switch on the logging before the first
allocation is done. Don't forget class constructors do
allocations as well!
				  24 CSEDSTR


The class for manipulating strings. Instead of providing the formal
syntax we will clarify things by supplying a large number of examples.

Class name: EDSTR
Prototypes are in "CSEDSTR.H"


   #include "csedstr.h"

   main(void)
   {
      EDSTR str;
      str=" A test ";       // Assign a string
      str.upper();	    // Convert to upper case
      str.lower();	    // Convert to lower case
      str.trim();	    // Remove all leading and trailing blanks
			    // str contains now: "a test";
      str+=" At the end ";  // APPEND at the end
			    // str contains now: "a test At the end"
      int i=-345;
      str=i;		    // Convert the integer value to string
			    // str contains now: "-345";
      str="1001";
      i=str;		    // Assign string to integer
      str="A line";
      str.strip("ijkl");    // Stripping the characters i,j,k,l from
			    // str.
			    // Str now contains: "A ne";
      str="A line";
      str.filter("ijkl");   // Allow only the characters i,j,k,l in
			    // str.
			    // Str now contains: "li";
      str="The quick brown fox";
      str[4]='Q';           // Str now contains: "The Quick brown fox"

      EDSTR str2="by C++ !";
      str="Made possible ";
      str=str+str2;	    // Str: "Made possible by C++ !";

      if( str<str2)   ..
      if( str>str2)   ..
      if( str<=str2)  ..
      if( str>=str2)  ..
      if( str==str2)  ..    // Comparisons are possible.
			    // Case is always ignored.
       gotoxy(5,20); clreol();
       str.edit(15,25); // User can edit the string.
			// Only 15 characters are displayed.
			// The maximal length of the string
			// is 25 characters.
			// The string will scroll if it
			// becomes longer then 15 characters.
			// 'Enter' or 'Escape' will
			// terminate the function.
  }
				 25 CSWINDOWS

25.1 Introduction

The WINDOW class is an implementation of the well known
character-based text windows.

The window toolbox can be used in two distinct ways:
    1) through the use of 'old-fashioned' C functions.
    2) as a C++ class.

The member functions of the WINDOW class are fine for manipulating
one instance of the class, but	the C-functions seem to have the
advantage when dealing with more then one instance.
E.g. shifting from one window to another or removing all windows at
once involves more then one instance of the WINDOW class, so this is
done by C-functions rather then class member functions.

The examples above already gives you an impression of which
functions to use when. When you are dealing with ONE window use the
class, in all other cases use the global C functions.
For the sake of completeness, the syntax of all the C-style functions is
given, but whenever possible use the class member functions.

For those who are becoming nervous:
    - You normally will need only two very simple C functions.
    - A few examples will make things clear!


25.2 General Information

Class name: WINDOW
Prototypes are in "CSWINDOW.H"

    - Every window is uniquely identified by a positive integer number.

    - In practice the number of windows an application can use is only
	limited by the available amount of memory.

    - Windows can 'overlay' each other. When an underlying window is
	called to be brought to the top, some flickering may appear
	because the still overlying windows must first be removed, and
	later be replaced.

    - Checks are made to see if the window which has to be brought to
	the top is overlaid. If not, it will become immediately active.

    - The windows are 'chained' together. The windows which are used
	last are closest to the end of the chain. When you are worried
	about performance:
	    a) try not to activate a window which is overlaid,
	    b) use only windows close to the end of the chain.
	However, under normal circumstances you will never have to
	worry about performance, because the window functions are
	very fast.


25.3 The C++ version, the class WINDOW


//  Example:

  #include "dos.h"
  #include "cswindow.h"

  main(void)
  {
    WINDOW demo;		// Declare a window

    demo.height(10);		// The number of lines
    demo.width(40);		// The number of columns
    demo.head(" A Demo Window ");// A text in the border
    demo.shadow(SHADOW);	// Make a shadow
    demo.activate();		// Display the window

    gotoxy(5,5);		// Write the famous
    cprintf(" Hello World !! ");// words in the window.

    waitkb(5000);		// Wait 5 seconds.

  }




25.3.1 Syntax of the MEMBER functions

void top(int)	Sets the top row of the window. If you enter -1 the
		window is centered vertically on the screen.
		Default is -1.
void left(int)	    Sets the left column if the window. If you enter -1 the
		    window is centered horizontally on the screen.
		Default is -1.
void height(int)    Sets the height of the window.
		Default is 24.
void width(int) Sets the width of the window.
		Default is 80.
void set_dim(int top,int left,int height,int width)
		This set all four at once with this function.
void border(int)    Sets the border type. Four types are pre-defined in
		    the header file CSWINDOW.H.
		1: W_BORDER_NONE    No border.
		2: W_BORDER_SINGLE  Border is a single line.
		3: W_BORDER_DOUBLE  Border is a double line.
		4: W_BORDER_SPACE   Border made out of 'space'
				    is drawn around the
				    window.
		The default is BORDER_SINGLE.
void head(char *)
		Sets the heading text of the window. This text is at
		center of the top of the window, but only if a border is
		The maximum length of this string is 49 characters.
		The default is the empty string.
void shadow(int)
		A value of 0 means no shadow, any other value will
		create a shadow. In CSWINDOW.H are two values
		pre-defined.
		1: NO_SHADOW	Don't display shadow.
		2: SHADOW	Display shadow.
		The default is NO_SHADOW.
void activate(void)
		This makes the window appear on the screen, on top
		of all other windows. The first time it will also create
		the window. If the window already exists, but is lying
		beneath other windows, it is shifted to the top.
void border_color(int)
void screen_color(int)
		Sets the text-attribute of the border and the text
		screen. Notice: the text ATTRIBUTE. This means
		both the foreground and the background colors are
		set. This is implemented through a call to Borland's
		TEXTATTR() function. See their documentation for
		more information. You can also use the
		'make_color()' function defined in 'cstools'.
int border_color(void)
		Returns the text-attributes of the border.
int screen_color(void)
		Returns the text-attributes of the screen.
void clear(void)    Clears the text screen.
		This is done automatically when the window is first
		created.
void remove(void)
		Remove the window permanently. The previous
		active window will become the active window again.
void remove_all(void)
		Removes ALL windows.
int adjust(void)    Even if the window already exists and is visible on
		    the screen, you can make changes. Just call the
		    appropriate functions ( e.g. height() to change the
		    height etc.) and when you are done call 'adjust()'. The
		    window will then change accordingly. The function
		    returns TRUE if successful FALSE otherwise.
int previous(void)
		Returns to the previous active window. That is: the
		window that was active before the current window
		became active. The function returns TRUE if
		successful FALSE otherwise.
int user_adjust(void)
		This function gives the application user control over
		the window. He/she can change the position of the
		window with the cursor keys and the size through use
		of the control-cursor keys. The function ends when
		ENTER is pressed. The function returns TRUE if
		successful FALSE otherwise.
void auto_delete(int)
		Sets the auto delete option. If called with a value
		unequal to 0, the window will be removed if the class
		instance is destructed. Otherwise the window will
		remain on the screen even if the class instance no
		longer exists. The default is ON.
void scroll(int ROW,int COL)
		Make the text in the window scroll.
		ROW:	scrolling up/down. A positive value means
			up (e.g. line 2 will become line 1).
		COL:	scrolling left/right. A positive value means to
			the right (e.g. column 1 will become
			column 2).
void get_dim(int *row,int *col,int *h,int *w)
		Returns the size-related-parameters of the window.
		The values returned do include the border but NOT
		the shadow.
		*row:	Top row.
		*col:	Left column
		*h:	height
		*w:	width
		These values need NOT be the same as the ones
		you assigned, because the window is adjusted to the
		limitations of the screen. (e.g. if you set height to 50,
		this function will tell you it is 25. )
void get_in_dim(int *row,int *col,int *h,int *w)
		The same as the previous function but now the
		returned values do NOT include the border and do
		NOT include any shadow. So, the inner dimensions
		are returned.
void work_size(int *height,int *width)
		Same as the previous function, but only the height
		and the width are returned.
int number(void)
		Returns the number of the window. Only needed if
		you are using the C functions as well.
25.4 The C version

As stated before, every window is identified by a unique integer
number. In the C++ version you don't have to worry about this number.
In the C version however, it is of prime importance. If a function has
parameters, the first parameter is always the number of the window you
are referring to.

As pointed out in the introduction you are supposed to use the class
functions whenever possible. In fact there are only two functions of the
C version you should use.

These are:

int win_current(void);
		Returns the number of the current active window.
		You might need this number if you want to return to
		this window somewhere later on in your application.
int win_shift(int num);
		Make window 'num' the active window. Useful in
		combination with the previous function.


In rare cases:

void win_remove_all(void);
		Remove all windows.
int win_default(void);
		For writing outside any existing window.

25.4.1 Example

Problem:
You want to write an error handling function which writes a message at
the bottom line of the screen and, after some waiting, returns to the
original window and continues. The problem is, the function can be
called from 'anywhere' and at the moment you are writing the function,
it is not clear to which window to return.



#include <......>

WINDOW w_error; 		// Window for the messages, global...

void disp_error(char *s)
{
    int old_window=win_current();   // The window the function
				// is called from.
    w_error.activate(); 	// Make the error window
				// the active window.
    cprintf("\n\r %s ",s);      // Print the text.
    wait(2500); 		// Wait 2.5 seconds.
    win_shift(old_window);	// Return to the original window!
}

main(void)
{
    w_error.set_dim(23,1,3,80);
    w_error.head(" E r r o r s ");
    w_error.activate(); 	// The window permanent visible
				// A lot of other windows
				// The program calls.
}


Note:	In this simple example the 'previous' function also would have
	solved the problem. In more complicated cases however, this
	method can very well be the only solution.


25.5 Syntax of the C functions

int win_make(	int& num,
		int border_color,
		int screen_color,
		char *head,
		int top,
		int left,
		int height,
		int width,
		int border,
		int shadow);


Parameters:
		num The number of the new window. If a window
		    with this number already exists it is deleted.
		    In the header file CSWINDOW.H is
		    predefined W_NEW. If you call the function
		    with 'num' equal to W_NEW a new window
		    is created and num is changed into a
		    number which was formerly not used. To
		    make this possible num is passed by
		    reference.
		border_color
		screen_color
			Sets the text-attribute of the border and the
			text screen.
			Notice: the text ATTRIBUTE. This means
			both the foreground and the background
			colors are set. This is implemented through
			a call to Borland's TEXTATTR() function.
			See their documentation for more
			information.
		head	Sets the heading text of the window. This
			text is displayed at the center of the top of
			the window, but only if a border is present.
		top	Sets the top row of the window. If you enter
			-1 the window is centered vertically on the
			screen.
		left	    Sets the left column if the window. If you
			    enter -1 the window is centered horizontally
			    on the screen.
		height	Sets the height of the window.
		width	Sets the width of the window.
		border	Sets the border type. Four types are
			predefined in the header file CSWINDOW.H.
			1: W_BORDER_NONE    No border.
			2: W_BORDER_SINGLE  Border is a single
					    line.
			3: W_BORDER_DOUBLE  Border is a
					    double line.
			4: W_BORDER_SPACE   Border made out
					    of 'space' is
					    drawn around the
					    window.
void win_remove(void)
		Remove the active window from the screen. The
		previously active window will become the active
		window again.
void win_remove_all(void)
		Removes ALL windows.
int  win_shift(int num)
		Will shift window with number 'num' to the top. The
		function returns TRUE if successful FALSE otherwise.
int  win_present(int num);
		Returns TRUE if window 'num' exists, FALSE
		otherwise.
int  win_first_unused(void)
		Returns the lowest positive number which is not 'in
		use' by a window.
int  win_current(void)
		Returns the number of the current active window. If
		no window is active it returns -1.
void win_clear(void)
		Clears the current active window.
int win_adjust( int num,
		int border_color,
		int screen_color,
		char *head,
		int top,
		int left,
		int height,
		int width,
		int border,
		int shadow);
		Is used to make changes to an existing window. The
		parameters are the same as with win_make(). Call
		this function with the parameters set to the newly
		desired values and your window will change
		accordingly. The function returns TRUE if successful
		FALSE otherwise.
int win_previous(void)
		Returns to the previous active window. That is: the
		window that was active before the current window
		became active. The function returns TRUE if
		successful FALSE otherwise.
int win_user_adjust(void)
		This function gives the application user control over
		the current active window. He/she can change the
		position by the cursor keys and the size by the
		control-cursor keys. The function ends when ENTER
		is pressed. The function returns TRUE if successful
		FALSE otherwise.
void win_scroll(int num, int ROW,int COL)
		Make the text in the window 'num' scroll.
		ROW:	scrolling up/down. A positive value means
			up (e.g. line 2 will become line 1).
		COL:	scrolling left/right. A positive value means to
			the right (e.g. column 1 will become
			column 2).
void win_get_dim(int num,int *row,int *col,int *h,int *w)
		Returns the size-related-parameters of window 'num'.
		The values returned do include the border but NOT
		the shadow.
		*row:	Top row.
		*col:	Left column
		*h:	height
		*w:	width
		These values need NOT be the same as the ones
		you assigned, because the window is adjusted to the
		limitations of the screen. (e.g. if you set height to 50,
		this function will tell you it is just 25. )
void win_get_in_dim(int num,int *row,int *col,int *h,int *w)
		The same as the previous function but now the
		returned values do NOT include the border and do
		NOT include any shadow. So, the inner dimensions
		are returned.
win_work_size(int num,int *height,int *width)
		Same as the previous function, but only the height
		and the width are returned.
int win_default(void)
		Not a window at all. Intended for writing outside any
		existing window. The x,y coordinates are set back to
		the full screen, as it is when you are working without
		windows. The software represents this virtual window
		by number -1. This number can also be returned by
				the win_current() function.25.6 Working within a Window

Working within a window is the same for both versions. All the above
mentioned functions are built around the compiler-supplied function
window(). This means you can use all Borlands I/O functions that are
intended to work relative to a window.

The most important ones are:
		gotoxy();   // To move the cursor
		putch();    // Displaying a character in the window
		cputs();    // Displaying a string
		delline();  // Delete a line
		insline();  // Insert a line
		clrscr();   // Clears the window
		clreol();   // Clears until end of line
		getche();   // Read a character
		cgets();    // Read a string
		cprintf();  // Window version of printf()
			    // Don't forget, you need "\n\r "
			    // to go to the start of the next line.
			    // cprintf("\n "); will put the cursor
			    // one line down, without going to the
			    // start of that line.



				  26 CSMENU


26.1 General Information

There are a lot of ways to implement a menu system. One very
common method concentrates on directly calling functions from within
the menu. This is not the method used here, because it seems to limit
its usefulness.

This menu system returns an integer value when an option is chosen.
The idea is to make the return value equal to a certain function key
connected with the option. The application user can then browse
through the menus and select an option or hit the function key directly.
In both cases your program has to deal with the same integer value
which can easily be processed by applying a 'switch' statement.

Examples will explain things further.

There is no difference between a menu and a sub-menu. The definition
is the same in both cases. The difference lies in the application of a
'connect' function which makes one menu the sub-menu of another.

There are no limitations to the amount of menus you can use. The
menus can be connected to arbitrary depth.

The menus 'remember' which option was selected the last time the user
was browsing through it. This option is again highlighted when the
menu is entered.




26.2 Defining a menu

int type(int hor_or_ver)
		Sets the type of the menu. There are two types:
		horizontal or vertical. In CSMENU.H two constants
		are predefined:
		MENU_HOR for a horizontal menu and
		MENU_VER for a vertical menu.
		The default is MENU_VER.
void hold(int HoldRelease)
		A menu can be permanent on the screen or can be
		automatically removed after being used. This function
		allows you to control this. In CSMENU.H two
		constants are predefined:
		MENU_HOLD makes the menu remain on the screen
		    and
		MENU_RELEASE which removes the menu
		    afterwards.
		The default is MENU_RELEASE.
void relative_pos(int ScreenCursor)
		The position of the menu can be set relative to the
		screen or relative to the cursor position at the
		moment of creation. ( Relative to the cursor is very
		convenient for submenus. ) In CSMENU.H two
		constants are predefined:
		TO_SCREEN for positions relative to the screen and
		TO_CURSOR for positions relative to the cursor.
		The default is TO_SCREEN.
void top(int w)
void left(int w)
		Sets the left column and the top row of the left-top
		corner of the menu. The coordinates are relative to
		the screen or the cursor, depending on the
		application of the relative_pos() function. If
		relative_pos is set to TO_SCREEN a value of -1 will
		center the menu.
void width(int)
void height(int)
		Sets the height and the width of the menu. If you
		don't use them or use zero, the appropriate values
		are calculated automatically. Advise: don't use them.
int add_option(char *option,int key)
		Adds an option to the menu. The order in which they
		appear in the menu is the same as the order in which
		they are defined. The string 'option' defines the
		option-string. You can use a '~' to assign a key to the
		option. The return value is the value 'key'.


	    Example:
		MENU m1;
		m1.add_option(" Se~tup ",123);
		// Defines option ' Setup ' as the first option
		// of menu m1. This option can also (apart
		// from using the cursor keys) being
		// selected by pressing 't' or 'T'.
		// If chosen, the menu system will return
		// the value 123.


void enable_option(int num,int YesNo)
		With this function you control whether-or-not an
		option can be selected.
		Parameters:
		num Option number 'num'. The first option is
		    number ONE.
		YesNo	YesNo=TRUE means option is selectable.
			YesNo=FALSE means option can NOT
			being selected.
		By default all options are enabled.
void enable_option(int YesNo)
		The same as the previous function, but applied to the
		last added option.
void border_color(int)
void screen_color(int)
void option_color(int )
void key_color(int )
		Sets the screen ATTRIBUTE of respectively the
		-border of the menu,
		-the 'normal' text in the menu, the not selected
		    options.
		-the option which is currently selected
		-the hot key connected with the option
		Acceptable defaults are provided, depending on the
		type of screen.
void color(int border, int screen, int option, int key)
		Sets all four colors at once.


26.3 Connecting menus

The previous described functions are all concerned with defining just
one menu. Of course you want to use a more complex menu structure
which includes several sub-menus. The method to accomplish this is a
simple one. Just define all submenus with the previously mentioned
functions and then 'connect' them by calls to the connect(int, MENU &)
function.

int connect(int num, MENU &m2)
		Makes menu 'm2' a sub menu of this menu, 'dangling'
		under option 'num'. The first option is number ONE.


	   Example:
	       #include "cswindow.h"
	       #include "cskeys.h"
	       #include "csmenu.h"

	       MENU m1,m2;
	       m1.add_option(" File ", ALT_F);
	       m1.add_option(" Setup ",ALT_S);
	       m1.type(MENU_HOR);
	       m1.left(1);
	       m1.top(1);
	       m1.border(BORDER_NONE);
	       m2.add_option(" ~Load ", F3);
	       m2.add_option(" ~Save ", F2);
	       m2.left(1);
	       m2.top(1);
	       m2.relative_pos(TO_CURSOR);
	       m1.connect(1,m2);



26.4 Displaying the menu

void standby(void)
		If the menu is defined, it can be put on the screen by
		calling the standby() function. This will make all the
		(sub-) menus which are defined with MENU_HOLD
		become visible.


26.5 Using menus

There are two ways to use the menu system:

1) The straightforward method.
		This is by a call to:
		    int rc=choose(int &option).

		The user can select an option by using the cursor
		keys, escape, enter etc..

		Afterwards rc indicates the way the menu is left.
		If rc==ESCAPE the menu is left by pressing the
		escape key, 'option' is undefined.
		If rc==ENTER the menu is left by pressing 'enter',
		and 'option' contains the return value corresponding
		with the selected option.
2) The hard way.
		In some, more complicated, cases the simple
		choose() function will not do, as is made clear by the
		following problem.

		Problem:
		    Let's say you have a program in which the user
		    can load a file by pressing F3. He also can reach
		    the same option by browsing through the menus.
		    Typically you want the load option to remain
		    highlighted on the screen while your user is
		    typing in the file name. How to accomplish this?
		Solution:
		    The int show(int key) function is intended to
		    solve this problem. Show(key) searches through
		    the menus looking for the value 'key'. It returns
		    TRUE if 'key' is found, FALSE otherwise.
		    Assuming the required value is found, all the
		    involved (sub-)menus are automatically
		    displayed. Afterwards things can be brought back
		    to normal by a call to standby().

	      Example:

	     #include "cswindow.h"
	     #include "cskeys.h"
	     #include "csmenu.h"

	     MENU m1,m2;
	     m1.add_option(" File ", ALT_F);
	     m1.add_option(" Setup ",ALT_S);
	     m1.type(MENU_HOR);
	     m1.left(1);
	     m1.top(1);
	     m1.border(BORDER_NONE);
	     m2.add_option(" ~Load ", F3);
	     m2.add_option(" ~Save ", F2);
	     m2.add_option(" e~Xit ", ALT_X);
	     m2.left(1);
	     m2.top(1);
	     m2.relative_pos(TO_CURSOR);
	     m1.connect(1,m2);

	     m1.standby();  // Finally display something.

	     int key;
	     do
	     {
	       key=cskey();   // Read a key
	       // ESC means browsing through the menus.
	       if(key==ESC) m1.choose(key);
	       // key now contains the option, either
	       // by directly typing it in or selected
	       // from the menu.

		switch(key)
	       {
		 case F3:   // Loading a file.
		  m1.show(F3);
		  // Both the menus are now being
		  // displayed.
		  // The option 'load' is highlighted.
		  //
		  // You can now call the load function.
		  load_function();   // Not defined.
		  //
		  m1.standby();
		  // Bring the menu system back in its
		  // default status.
		  break;
		 case F2:   // Saving a file.
		  // some
		  // code to
		  // save.
		  break;
	       }
	     } while (key!=ALT_X);  // Use ALT_X to terminate loop.



Syntax:

int choose(int &option)
		Use: int rc=choose(option);

		The user can select an option by using the cursor
		keys, escape, enter etc.. Afterwards rc indicates the
		way the menu is left. If rc==ESCAPE the menu is left
		by pressing the escape key and 'option' is undefined.
		If rc==ENTER the menu is left by pressing 'enter',
		and 'option' contains the return value corresponding
		with the selected option.
int choose_hold(int &option)
		Same as choose() but the selected option and the
		selected (sub)-menu(s) remain visible after the
		function has returned.
int show(int key)
		Show(key) searches through the menus looking for
		the value 'key'. It returns TRUE if 'key' is found,
		FALSE otherwise. Assuming the required value is
		found, all the involved (sub-)menus are automatically
		displayed. Afterwards things can be brought back to
		normal by a call to standby().


26.6 Removing the menu

void remove(void)
		By calling the remove() function the menu, including
		all sub-menus, will be removed.




				  27 CSPANEL


27.1 Purpose

The PANEL class is intended to provide data entry screen much like
the ones used by dBASE.


Class name: PANEL

27.2 General Information

    - This class is publicly derived from the window class. This implies
	that you can use all the functions from the window class to
	manipulate the panel window.
    - The default panel will appear both horizontally- and
	vertically-centered. The destructor of the class will remove, if
	needed, the panel and all fields.
    - The maximum number of fields in the panel is set to 35 by a
	preprocessor constant MAX_FIELDS. This value can NOT be
	increased. The variable is defined in the CSPANEL.H file.
    - The fields are numbered. The first field is numbered ZERO.
    - Fields can be made 'protected'. This means you cannot alter the
	contents of the field. The protection can be changed between
	successive calls to the read function (see below).
    - A 'changed' function is provided to determine whether-or-not
	something has been altered.
    - Functions are available to set the EXIT key and the EXIT field(s).


27.3 Public member functions

void field_color(int col_att)
		Sets the default text ATTRIBUTE of each new added
		field.
		Meaning:    each field which is added to the panel
			    after a call to field_color() gets color
			    col_att, unless this color is explicitly
			    overwritten.
int field_color(void)
		Returns this field attribute.
void color(int border,int screen,int field)
		Sets the colors of the border, the screen and the
		field.
int add_field(	int top,
		int left,
		int color,
		int length,
		TYPE&PARM)
		This function adds an additional field to the panel.
		The function returns the number of fields defined so
		far (including the one you just added.)
		Parameters:
		top:	    The row of the field, relative to the panel.
		left:	    The left column of the field, relative to the
			    panel.
		color:	The color ATTRIBUTE of the field.
		length: The number of positions of the field.
		TYPE&PARM:  One of the following:
		    TYPE    PARM
		    char    *s	For a string field
		    int     &i	For an integer field.
		    long    &l	For a long field.
		    float   &f	For a floating point field.
		    double  &d	For a double field.

int read(void)
		When the panel is adequately defined, the panel-user
		can be given control by a call to read(). The function
		returns when the user hits the exit-key or the ESC
		key. FALSE is returned if the panel is left by ESC,
		TRUE otherwise.
int changed(void)
		Returns TRUE if the panel is actually changed during
		the last read. If nothing is changed, the function
		returns FALSE.
int protect(int ebd)
		Intended to create a field which can NOT be edited.
		There are three options:
		- EDIT	    The default, the field can be edited.
		- BROWSE    The cursor will not skip this field and
			    you can let the string scroll but are not
			    allowed to alter the contents.
		- DISPLAY   The cursor will skip this field. The initial
			    value will be displayed. This is applied
			    to the last added field.
		The return value is TRUE if successful, FALSE
		otherwise.
int protect(int FieldNum, int ebd)
		Same as protect(int ebd) but applied to field
		FieldNum.
int exit_key(int KEY)
		Sets the key which makes the panel become
		'accepted'. The default is ENTER in which case the
		ENTER key cannot be used to skip from one field to
		another. The ESC-key will always terminate the panel
		and will make the read function returns FALSE. The
		allowable keys are the ones defined in CSKEYS.H.
int exit_field(int FieldNum, int YesNo)
		Normally the panel can be terminated no matter on
		which field the cursor is placed. After a call to
		exit_field(), the panel can only be terminated with the
		cursor	standing on a field which is marked as an
		'exit-field'.
		Calling exit_field(FieldNum,TRUE) marks field
		FieldNum as 'exit-field. Exit_field(FieldNum,FALSE)
		removes the mark.
int exit_field(int YesNo)
		Same as exit_field(FieldNum,YesNo) except that it is
		applied to the last added field.
void escape_off(void)
void escape(int True_of_False)
void escape_on(void)
		Escape_off() or escape(FALSE) disable the escape
		key. The panel can only be left by the 'exit-key'.
		Escape_on() or escape(TRUE) enables the escape
		key. This means the panel can be left by hitting
		escape.
void remove(void)
		Remove the panel and all fields. All dynamically
		allocated memory is freed. This is also done by the
		destructor off the class.
void date(int type)
		All the basic data types are automatically recognised
		by the add_field function. This is not so for dates. If
		you want a string to be treated as a date you have to
		call this function after the add_field() function.


27.4 Dates

This paragraph describes the value of 'type' in the date(type) function.

There are several different formats supported:
Formats and the examples for April/25/1993.

The formats are build out of:
M   Month
D   Day
Y2  Year 2 positions
Y4  Year 4 positions

		FORMAT: EXAMPLE:

		MDY2	    04/25/93
		MY2D	    04/93/25
		Y2MD	    93/04/25
		Y2DM	    93/25/04
		DMY2	    25/04/93
		DY2M	    25/93/04
		MDY4	    04/25/1993
		MY4D	    04/1993/25
		Y4MD	    1993/04/25
		Y4DM	    1993/25/04
		DMY4	    25/04/1993
		DY4M	    25/1993/04

	  EXAMPLE:

	 main(void)
	 {

	   char date_field[]="25/04/93";
	   PANEL pan;
	   pan.add_field(4,6,date_field);
	   pan.date(MDY2);

	 }



27.5 Fields

The fields in the panel are instances of an independent class. They are
derived from both the WINDOW class and the EDSTR class. This
means that  you can apply all the functions from these classes to each
field independently.

The modify a field you have the use the mod_field macro.


      EXAMPLE:
       // Suppose you want to change the color of a
       // specific field.

       #include "cspanel.h"

       main(void)
       {

	  float balance=-345.87;
	  int c_in_debt=make_color(RED,BLACK);

	  PANEL pan;
	  pan.add_field(2,3,10,balance);
	  if(balance<0)
	  { // display the figures in red if the
	    // balance is negative.
	    pan.mod_field.text_color(c_in_debt);
	  }

       }





The purpose of the macro is to hide a peculiar syntax.

The 'pan.mod_field.text_color(c_debt)' is expanded into:
		pan.field().text_color(c_debt)

If you have no objection against a syntax like that you can use it
directly. In fact, if you want to change a field other then the last-added
you have to use the function:

		FIELD& field(int num)


      EXAMPLE:
     // Suppose you want to put a border around field number
     // three ( Remember the first field is field ZERO.)

     PANEL pan;

     pan.add_field.....   //  Adding
     pan.add_field.....   //  Some
     pan.add_field.....   //  Fields
     pan.add_field.....   //
     pan.add_field.....   //

     pan.field(3).border(BORDER_SINGLE);
     // Change the color of the fourth field





Functions:
		FIELD& field(void)  To change the last added field.
		FIELD& field(int num)	To change field 'num'.
Macro:
		mod_field	    Same as field().


27.6 Using the panels

A panel basically consists of a window and a number of fields. The
fields can be changed by the 'mod_field' macro and the shape and size
of the panel-window can be altered with the functions from the
WINDOW class. However there is no special function to add normal
text in the panel. Of course this is not needed because the normal
gotoxy() and cprintf() functions can be used for that. But don't forget to
'activate' the panel before you write text in it.

    Example:

	#include "cspanel.h"
	main(void)
	{

	   char str[]=" Some string ";
	   char da[]="30/03/1987";
	   PANEL panel;

	   panel.set_dim(-1,-1,13,55);	    // These are
	   panel.head(" A demo  ");         // all functions
	   panel.border(BORDER_DOUBLE);     // from the WINDOW class.

	   panel.activate();		    // DON'T FORGET THIS !!!
					    // The panel is now visible
					    // (Although without fields)
	   gotoyx(2,10); cprintf("String: ");   // Put text in the panel.
	   panel.add_field(2,20,25,str);	// Add a field.

	   gotoyx(7,10); cprintf("Date: "); // Put text in the panel.
	   panel.add_field(7,20,da);	    // Add a date field.
	   panel.date(DMY4);		    // Date Format.
	   panel.exit_key(F10); 	    // Leave panel with F10.

	   panel.read();		    // Edit the data.

	}

// Note: gotoyx() is used instead of gotoxy().
// This is easier because the coordinates of the
// add_field() functions also work in that way.



27.7 Data validation

At the moment of writing the capabilities for data validation are limited.
There exists a option to supply a maximum and a minimum value.
Apart from that, there is a 'picture()' function to supply some crude sort
of template. It isn't perfect, but it will do in most cases.

Without 'anything' the next table applies:

    date:   Are validated correctly, including leap-years.
    int:    Accepts everything between -99999 and 99999.
    long:   Accepts everything between -9999999999 and
	    9999999999.
    float and double:
	    No checks for precision underflow or overflow. Accepts
	    exponents between -999 and +999. No limits to the
	    number of digits in the mantissa.
    string: No limitations.
    char:   Everything of length 1 is accepted.


When the user is editing a field, he/she is only allowed to skip to the
next field if the value is considered 'valid'.
When the 'escape' key is not disabled, the user can leave the panel
with invalid values by hitting 'escape'.


27.7.1 Min and Max

Syntax: void set_max(...);
	void set_min(...);


Every type of field has its own set of functions to supply a maximum
and a minimum value. These are inline functions which accept a type of
parameter which may differ from the field type as long as it makes
sence. E.g. a field of type 'long' has a set_max() function which accepts
a 'int' but does not have a function which accepts a string.



// Example Data Validation

#include "csa.h"

void main(void)
{

    PANEL panel;
    int value=125;

    panel.set_dim(-1,-1,19,75);     // Centre the panel on the screen.
    panel.head(" Data Validation  ");
    panel.border(BORDER_DOUBLE);
    panel.activate();

    gotoyx(6,10); cprintf("Integer field ");
    panel.add_field(6,30,4,value);  // Use 4 positions to edit 'value'.
    panel.mod_field.set_max(399);   // Set the maximum value to 399.
    panel.mod_field.set_min(99);    // Set the minimum value to 99.

    panel.read();		    // Do the editing.

}



27.7.2 Reset Max & Min

Syntax: void reset_max(void);
	void reset_min(void);

When the maximum or the minimum value is no longer required, it can
be removed by a call to reset_max() or reset_min().



// Example Reset max & min

#include "csa.h"

void main(void)
{

    PANEL panel;
    int value=125;

    panel.set_dim(-1,-1,19,75);     // Centre the panel on the screen.
    panel.head(" Data Validation  ");
    panel.border(BORDER_DOUBLE);
    panel.activate();

    gotoyx(6,10); cprintf("Integer field ");
    panel.add_field(6,30,4,value);  // Use 4 positions to edit 'value'.
    panel.mod_field.set_max(399);   // Set the maximum value to 399.
    panel.mod_field.set_min(99);    // Set the minimum value to 99.

    panel.read();		    // Do the editing.

    panel.mod_field.reset_max();    // Reset the maximum value.
    panel.mod_field.reset_min();    // Reset the minimum value.

    panel.read();		    // Edit again.

    panel.remove();		// Remove the panel from the screen.

}


27.7.3 Templates

Templates are used to specify more accuretly the format of the field.
This is done by describing the field position-by-position.

Next is a table indicating which symbols can be used in the template
and what they mean.

    n	    0123456789-+    Blank allowed.
    N	    0123456789-+    Blank NOT allowed.
    o	    0123456789	    Blank allowed.
    O	    0123456789	    Blank NOT allowed.
    p	     123456789-+    Blank allowed.
    P	     123456789-+    Blank NOT allowed.
    q	     123456789	    Blank allowed.
    Q	     123456789	    Blank NOT allowed.
    s	     -+ 	    Blank allowed.
    S	     -+ 	    Blank NOT allowed.
    A	     The characters from the alfabet. Blanks NOT allowed.
    a	     The characters from the alfabet including blanks, ,.;: etc.
    B	     A but displayed as capital. (Not implemented yet.)
    b	     a but displayed as capital. (Not implemented yet.)
    C	     A or O
    c	     a or n

Or in a table:

ͻ
	  12345     0     +-       blank    abcdefghijk      ~`!@#$%^   
	  6789  	  	    	         lmnopqrstu    &*()_=|\}{[]"
	 	 	  	    	           vwxyz         :;'?><,./  
Ķ
   n	    x	     x      x     	 x     	       	      
Ķ
   N	    x	     x      x     	       	       	      
Ķ
   o	    x	     x   	    	 x     	       	      
Ķ
   O	    x	     x   	    	       	       	      
Ķ
   p	    x	 	     x     	 x     	       	      
Ķ
   P	    x	 	     x     	       	       	      
Ķ
   q	    x	 	  	    	 x     	       	      
Ķ
   Q	    x	 	  	    	       	       	      
Ķ
   s	 	 	     x     	 x     	       	      
Ķ
   S	 	 	     x     	       	       	      
Ķ
   a	 	 	     x     	 x           x               x      
Ķ
   A	 	 	  	    	             x        	      
Ķ
   c	    x	     x      x     	 x           x               x      
Ķ
   C	    x	     x   	    	             x        	      
ͼ




A template can be applied to a field by calling the 'picture()' function.

Syntax:
	void picture(char *temp);

The template itself is respresented as a string.
When a template string is used, only the first 127 characters
from the ASCII set can be used (reliable) in editing the field!!


Example:

#include "csa.h"

void main(void)
{

    PANEL panel;
    int value=125;

    panel.set_dim(-1,-1,19,75);     // Centre the panel on the screen.
    panel.activate();

    panel.add_field(6,30,3,value);  // Use 3 positions to edit 'value'.
    panel.mod_field.picture("QOO"); // A 3 positions integer. No blanks.
				    // The first pos is not a '0'.
				    // No -+ allowed/

    panel.read();		    // Do the editing.

    panel.remove();		    // Remove the panel from the screen.

}


When a character other then one from the table, appear in the template
string, this character becomes a mandatory setting of the field at that
particular position.


Example:

PANEL panel;
char *str;

.......
panel.add_field(6,10,9,str);
panel.mod_field.picture("tel:AAAAA");
.....
...


In this example the string will be 9 positions of which the first 4 will be
'tel:'. The last 5 will be freely editable, although only the characters from
the alfabet are allowed.

This setup works fine until you want to use one of the template codes
as a literal. To facilitate in that, a 'special' character is supplied. This is
the '@' character.

E.g.: the template string "phone:AAAAA" will probably not do what you
want, because the p,o,n are interpreted according to the table above.
You have to use the string "@ph@o@ne:AAAAA" instead.

It is possible to supply only a limited number of options at a specific
position. For this, there is a special 'or' syntax.


Example:
...mod_field.picture("o.oo[e|E]o");


In this example there has to be a 'e' or a 'E' at the fourth position.
Nothing else will be allowed.
The or syntax takes the form of:
			     [option1|option2].

The "or's" can be nested:


Example:
...mod_field.picture("[oooooo|o.oo[e|E]o]");


The length of the options has to be the same. Note that 'e' is
of the same length as 'E' and 'oooooo'  is 6 positions just as
'o.oo[e|E]o'.

More then two options are also allowed:


Example:
...mod_field.picture("o.oo[f|F|e|E]o");



			     28 Registration Form




    Name:	__________________________________________

    Address:	__________________________________________

		_____________________________________

    City/State: __________________________________________

    Country:	______________________	 Zip: ______________

    Telephone:	(________)  _________ - ____________________


    Diskette preference (circle one):		5.25	     3.5
    If it's the same to you, please pick 3.5.



		    Number		Price F 	Price $
				    (Dutch Guilders)  (American Dollars)


    CS-Libraries	_________   at	f  125.-    or	    $  75.-

    Manual		__________  at	f   30.-    or	    $  20.-



    Total enclosed			f ______    or	    $ _____




    What would you like to see added or changed in the CS-Libraries?

    _____________________________________________________________
    _____________________________________________________________
    _____________________________________________________________
    _____________________________________________________________
    _____________________________________________________________




    Send completed form and check or money order to:

		Theo van den Bout
		P.O. Box 3303
		2280 GH RIJSWIJK
		The Netherlands


Note:	If this documentation is more then a year old, it is advisable to
	obtain a later version. Software, price and even address may
	have changed!
