

                                                                  April 1995





                            The CS-libraries


                            COMBIS
                            P.O. Box 3303
                            2280 GH Rijswijk
                            The Netherlands






    Copyright (c) 1994,1995 by Combis, the Netherlands.
        All Rights Reserved.
                                 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 Royalties and runtime limitations. . . . . . . . . . . . .
        2.4.3 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 . . . . . . . . . . . . . . . . . . . .
    25.7 File Browsing . . . . . . . . . . . . . . . . . . . . . . . . .

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 mainly of 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:
        Theo van den Bout
        P.O. Box 3303
        2280 GH Rijswijk
        The Netherlands

For urgent matters, phone me at +31703960172.
Please remember, it is GMT +100 over here.




2.3 Registering

When you send  $ 125 (125 American Dollars)
you will become a 'registered user'.

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


Send money, check or money order to:

                 Combis
                 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 THIS
SOFTWARE "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 SOFTWARE IS WITH YOU.  SHOULD
THE SOFTWARE 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 Royalties and runtime limitations

These libraries can be used in a commercial product without any
royalties as long as the number of copies sold annually does not
exceed 10.000.

No part of these libraries may be used in a product which is in any way
a competitor of the CS-Libraries.


2.4.3 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.
                                 4 Overview

This chapter 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:          A database class for 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.

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++ compilers. 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 support the huge memory model.


ͻ
 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 with BC 3.1
        - 'Undefined' characters with BC 4.x.
        - 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 the right type of 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. 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 chapter 8.)

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 considerablely.
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 considerably 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 8 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
Normally 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.

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().

The define() function of TBASE considers slacks up to 30% acceptable.
If it doesn't manage to find a page size which produces a slack of less
then 30%, it calls 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 again 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
                10.8.2 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' with the data
                from buffer 'buff'. Record 'rec' needs to exist, the
                function cannot be used to append a record.


                               11 BTREE-class


11.1 Introduction

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 the key values sorted. 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 a new type of variable. The
only thing to do is to 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 member 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);
    int   tBOF(void);
    int   tEOF(void);

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 of hte new BTREE.
                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 paragraph 11.3 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 is 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 8 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.
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 praragraph 11.4 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 close to a given value. 'key' is the key
                value searched for while 'Key' is the key value
                actually found. 'Data' is the data part belonging to
                'Key'.
                The suffix '_xx' has a meaning conform 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.
int   tBOF(void);   Returns TRUE if 'current' is pointing to the first entry
                    of the btree, FALSE otherwise. Behaviour is
                    undefined in case 'current' is NOT set!
int   tEOF(void);   Returns TRUE if 'current' is pointing to the last entry
                    of the btree, FALSE otherwise. Behaviour is
                    undefined in case 'current' is NOT set!



                                 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 can aid in building a nice database out of this. A
database which is capable of manipulating fields, maintaining indexes
and the alike.

As input it takes 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, they are slow,
    and 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 off with creating a database 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 it produces 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 paragraph 12.6 about
    '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.
    As a 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 previous 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 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 database 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.

To save disk space, the length of the key field in the BTREE
is only half of the field length in the main database.


12.7 When is a substring indexed?

First of all: tokenizing only applies for string fields. E.g. 'Tokenizing' a
float field seems 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.


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 the
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
'familiar' fields are read, the others are ignored.) This mechanism can
be used to make changes in the record layout.
Note: Because it's an ASCII file, the names of the fields can be
changed with a normal editor.


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 a load of runtime 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();        // Opens 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 paragraph
27.4.

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. When the
                pack() function is called all the marked records are
                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
                function can only be called when no index is active!
                When an index is active, an error is generated.
                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.
int order(void);    Returns the number of the current active index.
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.
       <Class>.order(UNSORTED);
       makes all the indexes inactive.

                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 tBOF(void); Test for Beginning Of File.
int tEOF(void); Test for End Of File.
                The functions return TRUE if the end is reached,
                (according to the active index) FALSE is returned
                otherwise.
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.  Nothing else 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


Assume 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.order(UNSORTED);      // Render all indexes inactive.
                            // Needed to use the go_to() function.

   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 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. (Chapter 10. )


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.
char *locate_rec(U32  rec);
char *locate_rec_d(U32  rec);
                Functions to return a pointer to record 'rec' directly
                into the buffer system. The returned pointer can be
                used to change the contents of a record but not the
                length. Please, read paragraph 10.8.2 about locating
                before using these functions.
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.

The function prototypes are in cstools.h.

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(int amount);
U32 csrand(U32 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   ALT_DELETE
     F2     SHIFT_F2    CTRL_F2   ALT_F2    KEY_2   ALT_HOME
     F3     SHIFT_F3    CTRL_F3   ALT_F3    KEY_3   ALT_CURSOR_UP
     F4     SHIFT_F4    CTRL_F4   ALT_F4    KEY_4   ALT_CURSOR_DOWN
     F5     SHIFT_F5    CTRL_F5   ALT_F5    KEY_5   ALT_CURSOR_RIGHT
     F6     SHIFT_F6    CTRL_F6   ALT_F6    KEY_6   ALT_CURSOR_LEFT
     F7     SHIFT_F7    CTRL_F7   ALT_F7    KEY_7   ALT_PAGE_UP
     F8     SHIFT_F8    CTRL_F8   ALT_F8    KEY_8   ALT_PAGE_DOWN
     F9     SHIFT_F9    CTRL_F9   ALT_F9    KEY_9   ALT_END
     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:
Prototypes in csmalloc.h.

   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.


25.7 File Browsing


The WINDOW class has a member funtion 'browse', which aid in
displaying help-files.


// Example


   WINDOW help;                     // Create Window for help text.
   help.set_dim(-1,-1,18,70);       // Set size and position.
   help.head(" Help Screen ");      // A Header.
   help.color(HlpBorCol,HlpScrCol); // Set the colors.
   help.browse("help.txt");         // Browse the help file.



The help file has to be an ASCII file.

Supported keys:

    ESC and ENTER:      Exit.
    Cursor Up:          1 line up
    Cursor Down:        1 line down
    Page Up:            1 screen up.
    Page Down:          1 screen down.
    Home:               Top of file.
    End:                End of file.

                                 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.

Function protypes are in csmenu.h.


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==ESC 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==ESC 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
Prototypes: cspanel.h.

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:    _____________________________________

    Telephone:  (________)  _________ - ____________________




                           Price $         Number      Amount
                     ( American Dollars )


    CS-Libraries          $ 100.-           ___      $ ________

    Manual                $  25.-           ___      $ ________


                                                     __________ +

    Total enclosed                                   $ ________




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

    _____________________________________________________________
    _____________________________________________________________
    _____________________________________________________________
    _____________________________________________________________
    _____________________________________________________________




    Please, send completed form and check or money order to:

                  Combis
                  P.O. Box 3303
                  2280 GH RIJSWIJK
                  The Netherlands


    Payment is also possible in US$ by SWIFT bank transfer to account:
                  43.47.74.537
                  Combis
                  P.O. Box 3303
                  2280 GH RIJSWIJK
                  The Netherlands

     But please, send the registration form also!




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!
