
This article is reprinted from the February 1991 edition of TechNotes/dBASE
IV.  Due to the limitations of this media, certain graphic elements such as
screen shots, illustrations and some tables have been omitted.  Where
possible, reference to such items has been deleted.  As a result,
continuity may be compromised.  

TechNotes is a monthly publication from the Ashton-Tate Software Support
Center.  For subscription information, call 800-545-9364.

Programming Printer Selection

Steve Koterski

Here is a short, modular routine to allow users of an application 
to set up or change the setup of their printer from within an application. 

 Thanks to the new self-contained file, DRIVERS.EXE, obtaining a printer
driver is a snap.  This enhancement to version 1.1 of dBASE IV  made it
pretty natural to develop a driver installation utility program, presented
in the next few pages.   It uses printer driver information found in the
dBASE IV Language Reference, and the compressed printer driver file,
DRIVERS.EXE.  The default print settings window design is a variation of
that found in the  manual, page 13-25.

The essence of the design of this module is transportability.  I use this
module in a number of applications, just plugging it into an existing menu
system.  It also lends itself well to inclusion in an Applications
Generator program.  Aside from the few dBASE IV specific operations, with a
little modification, it could even be transported to a dBASE III PLUS
application.  

Modularity

The concept of modular programming is important for the developer who needs
boilerplate routines that are common to a range of different applications. 
It also gains importance with the use of the greatly improved Applications
Generator in dBASE IV, where modules can easily be mixed and matched, and
common routines transported to various menu systems.

Modular programming involves program design that incorporates virtually
stand-alone code, independent of any particular application.  The program
design depends only on a parent application to run under.  Useful routines
such as printer configuration, file backup, and screen background design
lend themselves especially well to modular programming.  Programs that are
limited in application such as data entry, ad-hoc queries, and other
application or data specific operations are less adaptable.  A routine that
uses modular design will be one that can be "plugged into" almost any
application, with little to no modification of the code being necessary. 
It will also perform a generic function needed by nearly any application.

A Useful Example

Here now is an example of a program that transports easily into several
applications.  The purpose of this program is to provide an interface
similar to the printer selection menus found in the dBASE installation
program.  

There are four files used with this module: the database and index,
DRIVERS.DBF and DRIVERS.MDX; the memory file, ATTRIB.MEM; and the program
file itself, PRINTERS.PRG.

The data for the printer driver database comes directly from  Language
Reference, Appendix F.  The structure of the database takes into
consideration the long text of some entries in the Manufacturer and Model
columns, and the Driver field includes the printer driver file extension
(.PR2):

Field           Type    Width
BRAND           C       30
MODEL           C       40
DRIVER          C       12

Two index tags are used, BRAND and TEMP.  The BRAND TAG orders the database
by manufacturer, suppressing duplicate manufacturer names.  This is
necessary for showing only one occurrence of each manufacturer name in the
Brand popup picklist.  The second tag, TEMP, is a temporary tag, created on
the fly in the module, limiting the printer models displayed in the Model
picklist by matching the Brand field entries with the manufacturer selected
from the Brand picklist.  This is accomplished with the FOR clause:

                INDEX ON brand + model FOR brand = mbrand

The variable mbrand stores the manufacturer name selected from the Brand
picklist.  The TEMP tag is deleted from the MDX file prior to the
termination of the module.

A dBASE memory file, named Attrib.MEM, is also necessary.  In this file are
stored the default print settings for the application the module is used
in.  These settings take the form of memory variables, each corresponding
to a dBASE IV system memory variable.  The memory file is invoked once
during the module to allow the user to modify the default settings.  

The internal structuring of the main routine, Printers.PRG, is designed to
be as modular as possible.  The routine is sectioned off into partitions of
like operations.  This not only helps in design, but also in debugging and
future modification.  As we all know, without this internal structuring,
the program we find so familiar as we write the code can become virtually
unintelligible six months to a year later.

The actual code in Printers.PRG is intended to be as simple and
straight-forward as possible.  The only real exception to this is the
program flow during the activation and deactivation of the popup
picklists.  A consistent, meaningful naming convention for the memory
variables used in the routine facilitates easy control and release.  The
code was broken down into sections of unique operations wherever possible.

The first section establishes the small requirements needed by the module. 
It takes advantage of the dBASE IV function SET() to save original
environment settings that, if left unchanged, could adversely affect the
parent application.  The initialization of the module memory variables is
consolidated here to make identification and tracking of these variables
easier.  The printer driver database is opened here in work area ten. 
Since most applications utilize work areas from the lower numbered ones to
the higher, ten was chosen.  If select area ten is used by a parent
application, this will require modification.

The next section defines and activates the popup picklist Brand.  This
picklist displays the Manufacturer names from the Brand field.  The Brand
field, by necessity, has a duplicate Brand value for each Model made by a
different Manufacturer.  The display of duplicate entries is suppressed for
the popup using the unique index tag BRAND.  The selection of a printer
Brand is stored to the memory variable mbrand.

If there is more than one model for a particular brand, program flow skips
to the Model procedure to define and activate the Model picklist.  The
order is changed to limit the Models displayed with the index tag TEMP.  It
is generated here based on the value stored previously in mbrand.  If there
is only one model for a brand, that model is selected.

At this point the program flow can branch to one of three directions
dependent on user action.  If the user aborts by pressing Escape, control
returns to the Brand picklist, where another abort terminates the routine
or another Brand may be selected.  The second and third directions depend
on whether the user elects to "keep" the Model, or select a new one.  If
the user accepts the choice made, both picklists are deactivated and flow
proceeds to the driver file extraction.  If the user declines the selection
of Model, control returns to the Model picklist.  The content of the Driver
field corresponding to the selected Model is stored to the memory variable
zdriver with the LOOKUP() function.

The Drivers File & DOS Shelling

Once a Model is selected, the routine proceeds to two sequential
operations: the extraction of the printer driver file from the compressed
file DRIVERS.EXE, and the modification of the default print settings.

The DRIVERS.EXE file is a self-extracting depository of all the printer
driver files, compressed into one file.  Ordinarily, DRIVERS.EXE is used
only by the DBSETUP program.  But, it is possible to extract one or more
files with the appropriate DOS command.  The program does this by shelling
out to DOS from dBASE IV with the commands:

RUN DEL *.PR2 > NUL
RUN drivers &zdriver > NUL

The first of these commands deletes any existing driver files.  This
prevents the accumulation of driver files from running this module
frequently.  It is also necessary because the extraction prompts the user
when an existing file is to be overwritten by the extracted file, and it is
not possible to override or suppress this prompt from within dBASE IV.  The
second command executes the actual extraction, naming the file to be
extracted with the zdriver variable.  The combination of setting CONSOLE
off and redirecting the screen output to NUL suppress these operations from
the screen.

The next operation provides, in a window, entry fields to modify the
variables stored in the memory file.  Simple @.SAYs and @.GETs return the
new or unchanged values, and the variable contents are then stored to the
dBASE system variables so the module variables can be released.  The
variables then get resaved to the memory file. 

Closing Environment

The last step in the program flow is the Closing environment section.  Here
the external related settings are restored to the values saved in the
Opening environment section, memory variables specific to the module are
released, and the module terminates.

Program & System Memory Variables

The naming convention used for the memory variables in this module clearly
associates each variable with its destination system variable, and allows
for easy control and release.  The variables fall into two groups: routine
specific and printer specific.  Each of these two groups can be released as
a unit using the common first letter of the variable name  "z" for routine
and "p" for printer variables  and the ALL LIKE clause of the RELEASE
command.

CNTR()

The CNTR() User Defined Function (UDF), is incidental, the likes of which
you've probably encountered before.  Its sole purpose is the centering on
screen of messages of variable length, specifically those containing the
printer driver file name.  It is, itself, a portable module, but can be
limited severely depending on screen design.  It works only with text
displayed with @..SAY commands, those containing @..GET clauses require
literals for positioning.

Considerations

Certain conditions must be taken into consideration when using this module:

1.              The design presumes an application exists in a DOS directory of its
own and not in the dBASE directory.  The routine deletes all printer driver
files in the current directory.

2.              It will be necessary for the parent application to RESTORE the
print settings in the memory file and store the variable contents to the
dBASE system variables for the settings to have any effect in print
sessions.  Unless this is done, the settings will be put into the system
variables only after this module is run.

3.              The routine presumes the parent application uses no memory
variables that begin with "z" or "p".  Such variables are released en masse
by the routine with the ALL LIKE clause of the RELEASE command.

4.              One driver was deliberately left out of the module: the PostScript
driver.  This is because the added considerations associated with this
driver are beyond the scope of this module.

5.              The coordinates of the messages presume STATUS is set off.

6.              No consideration is given in the module to interactive help or
printer status.  It is presumed the parent application will have a help
module (if desired) and an error routine (strongly suggested).

Memory File

Initial contents of the memory file ATTRIB.MEM:


PPDRIVER    pub   C  " "
PPQUALITY   pub   L  .F.
PPPITCH     pub   C  "DEFAULT"
PPWAIT      pub   L  .F.
PPEJECT     pub   C  "NONE  "
PPCOPIES    pub   N           1  (1.000000000000000000)
PPEPAGE     pub   N        9999  (9999.000000000000000)
PPBPAGE     pub   N           1  (1.000000000000000000)
PPSPACING   pub   N           1  (1.000000000000000000)
PPLENGTH    pub   N          66  (66.00000000000000000)
PINDENT     pub   N           0  (0.000000000000000000)
PRMARGIN    pub   N          79  (79.00000000000000000)
PLMARGIN    pub   N           0  (0.000000000000000000)
PLOFFSET    pub   N           0  (0.000000000000000000)
PPORT       pub   C  "LPT1"

-------------------------------------------------------------------
PICKPRNT.PRG
*  Opening environment.
STORE SET("SAFETY") TO zsafety
STORE SET("CURSOR") TO zcursor
SET SAFETY OFF
PUBLIC zans
STORE "N" TO zans
STORE SPACE(4) TO pport
STORE " " TO zbrand,zmodel,zdriver
STORE  pdriver TO molddrvr
SELECT 10
USE drivers ORDER brand

*  Display picklist of printer brands.
DEFINE POPUP brand FROM 5,24 TO 17,56 PROMPT FIELD brand
ON SELECTION POPUP brand DO model
ACTIVATE POPUP brand

*  If new printer is selected, update configuration file.
IF zans = "Y"
                SET CURSOR OFF
                SET CONSOLE OFF
                RUN DEL *.PR2 > NUL
                RUN drivers &zdriver > NUL
                SET CONSOLE ON
                @ 22,0 SAY CNTR("Printer driver '" + zdriver + "' successfully
installed")
                @ 23,0 SAY CNTR("Press a key to continue")
                WAIT ""
                @ 22,0 CLEAR TO 23,79
ENDIF

*  Set printer defaults.
RESTORE FROM attrib ADDITIVE
DEFINE WINDOW printers FROM 5,12 TO 17,69
SET CURSOR ON

IF zans = "Y"
                @ 22,20 SAY "Enter printer port for this driver: " GET pport ;
                        PICTURE "@M LPT1, LPT2, LPT3, COM1, COM2, COM3"
                READ
                @ 22,0 CLEAR TO 22,79
ENDIF

ACTIVATE WINDOW printers

@  0,20 SAY "PRINTER DEFAULTS"

@  2, 1 SAY "PAGE SETTINGS"
@  3, 1 SAY REPLICATE("_",20)
@  4, 1 SAY "Offset from left "  GET ploffset  PICTURE "99"
@  5, 1 SAY "Left Margin..... "  GET plmargin  PICTURE "99"
@  6, 1 SAY "Right margin...."   GET prmargin  PICTURE "999"
@  7, 1 SAY "Indentation..... "  GET pindent   PICTURE "99"
@  8, 1 SAY "Page Length....."   GET pplength  PICTURE "999"
@  9, 1 SAY "Spacing.........  " GET ppspacing PICTURE "9"

@  2,22 SAY "PRINT SETTINGS"
@  3,22 SAY REPLICATE("_",27)
@  4,22 SAY "Begin printing on page." GET ppbpage   PICTURE "9999"
@  5,22 SAY "End printing on page..." GET ppepage   PICTURE "9999"
@  6,22 SAY "Number of copies......." GET ppcopies  PICTURE "99"
@  7,22 SAY "Eject page............." GET ppeject  ;
        PICTURE "@M BEFORE, AFTER, BOTH, NONE"
@  8,22 SAY "Wait between pages....." GET ppwait    &&PICTURE "N"
@  9,22 SAY "Text pitch............." GET pppitch ;
        PICTURE "@M DEFAULT, PICA, ELITE, CONDENSED"
@ 10,22 SAY "Quality print.........." GET ppquality &&PICTURE "N"
READ

*  Set system memory variables.
 ploffset = ploffset
 lmargin = plmargin
 rmargin = prmargin
 indent = pindent
 plength = pplength
 pspacing = ppspacing
 pbpage = ppbpage
 pcopies = ppcopies
 peject = ppeject
 pwait = ppwait
 ppitch = pppitch
 pquality = ppquality

IF zans = "Y" .AND. zdriver > " "
                 pdriver = zdriver
ENDIF

ppdriver = zdriver
SET PRINTER TO &pport
SAVE ALL LIKE p* TO attrib
DEACTIVATE WINDOW printers

*  Closing environment.
SET CURSOR &zcursor
SET SAFETY &zsafety
CLEAR
RELEASE ALL LIKE p*
RELEASE ALL LIKE z*
RELEASE POPUP brand,model
RELEASE WINDOW printers
RETURN


PROCEDURE Model
                *  Display picklist of printer models from selected brand.

                IF LASTKEY() <> 27
                        zbrand = PROMPT()
                        INDEX ON brand+model FOR brand=zbrand TAG temp 
                        SET ORDER TO temp
                        COUNT FOR brand = zbrand TO zcount
                        break=1

                        IF zcount > 1
                                DEFINE POPUP model FROM 5,19 TO 17,61 PROMPT FIELD
model
                                ON SELECTION POPUP model DO choice
                                ACTIVATE POPUP model
                                DELETE TAG temp
                                SET ORDER TO brand

                                IF zans = "Y"
                                        DEACTIVATE POPUP
                                ENDIF

                        ELSE
                                zbrand = PROMPT()
                                zdriver = TRIM(UPPER(LOOKUP(driver,zbrand,brand))) 
                                zmodel = TRIM(LOOKUP(model,zbrand,brand))
                                zans = " "

                                DO WHILE .NOT. zans $ "YN"
                                        zans = "N"
                                        @ 22,0 SAY CNTR("You have selected: " +
zmodel)
                                        @ 23,28 SAY "Is this correct? (Y/n)" get
zans picture "!"
                                        READ
                                        @ 20,0 CLEAR TO 22,79
                                ENDDO

                                IF zans = "Y"
                                        DEACTIVATE POPUP
                                ELSE
                                        SET ORDER TO brand
                                ENDIF
                        ENDIF
                ENDIF
RETURN


PROCEDURE Choice
                *  Store driver name from selected printer to memory variable.

                zmodel = TRIM(PROMPT())
                zdriver = TRIM(UPPER(LOOKUP(driver,zmodel,model))) 
                zans = " "

                DO WHILE .NOT. zans $ "YN"
                        zans = "N"
                        @ 22,0 SAY CNTR("You have selected: " + zmodel)
                        @ 23,28 SAY "Is this correct? (Y/n)" get zans picture "!"
                        READ
                ENDDO

                @ 20,0 CLEAR TO 22,79
                DEACTIVATE POPUP
RETURN          


FUNCTION Cntr
*  Center on screen character strings of variable length.

                PARAMETER string
                string2 = SPACE((80 - LEN(string)) / 2) + string
RETURN string2

*   EOF PICKPRNT.PRG


