$if 0
    ķ                        PowerBASIC v3.50
 Ĵ          DASoft          ķ
   Ķ    Copyright 1998     DATE: 1998-01-31 ķ
    FILE NAME   TINPUT  .TXT           by            
                               Don Schullian, Jr.                      
   ͼ                                          
  A license is hereby granted to the holder to use this source code in   
  any program, commercial or otherwise,  without receiving the express   
  permission of the copyright holder and without paying any royalties,   
  as long as this code is not distributed in any compilable format.      
   IE: source code files, PowerBASIC Unit files, and printed listings    
 ͼ 
   ͼ
$endif

 Ŀ
    fTinput$ and fTinputM$ both use the same TINPUT_?.INC files.   
 

 Before you get into all of this: The info in this file is vital but,
 despite the length of the file it is  NOT difficult to understand if
 you don't try to make it into something more than it is. The concept
 was taught to me by my guru in about 30 seconds on the margin of the
 daily news paper! So, if I can grasp it that fast, nuff said! d;D

 A program is not worth much if it cannot input data and instructions
 from the user; how this information  and data is input is one of the
 most important features of any program.  It should be as easy and as
 fool-proof as possible. Your job, as programmer, is to make all this
 happen.

 As there are several different types of data one all-purpose routine
 just doesn't make the cut!  On the other hand, to attempt to have an
 individual routine for each style is just too much to handle. I have
 come up with a happy medium:  a suite of routines that allows you to
 call a single function from within your program. It calls a specific
 handler for the data style required.  Sorta' like one stop shopping!

 There are 9 field styles. Eight of them provide specific support and
 the ninth is a general,  all-purpose field  that catches  everything
 else.  The fields are controlled by a string of data  that you send.
 The string is loaded into a TYPE by the function before it is passed
 to the specific handler.

 As you can see the first  8 bytes of the control string fit into the
 TYPE  but from position nine and on  you can also send more data for
 some of the styles that require it  or can use it.  This area of the
 string is known an the MASK.  Also, not all the members of the  TYPE
 are used by all the styles and  .Misc is a kind of catch-all member.
 Despite their comings and goings,  the members hold their purpose in
 life throughout.  Below is a generalized description for each member
 then a style by style discussion.

 Ŀ
  One important thing to remember is that fTinput assumes that the 
  incoming data is IN THE CORRECT FORMAT for the designated Style. 
 

 TYPE TinputTYPE             '
   Style  AS STRING * 1      ' field type ie: A,B,D,H,M,N,P,Q,T
   Row    AS BYTE            ' screen row
   Col    AS BYTE            ' left most screen column
   Cols   AS BYTE            ' N of visible columns
   MustBe AS BYTE            ' > 0 if field is mandatory
   Cased  AS BYTE            ' 0 = none 1 = UCASEed 2 = LCASEed
   Misc   AS BYTE            ' max length of string => Cols
   Just   AS BYTE            ' N of routine to call fJUSTIFY$ with
 END TYPE                    'Ĵ 9 bytes 

 .Style   always indicates which field driver to use
 .Row     always the starting screen row for the field
 .Col     always the left-most column(s) for the field
 .Cols    always the maximum visible columns of the field
          in most cases this is also the maximum length of the field
 .MustBe  if > 0 then the field is mandatory
          this field is used differently by each style
 .Cased   if 0 then no casing is done to the input
          if 1 then all input is UCASEd using UCASEstr
          if 2 then all input is LCASEd using LCASEstr
 .Misc    used differently by each style
 .Just    an extra byte for your use as it is NOT used by fTinput$

 
 ANYTHING GOES
 
  Style  = "A"         Anything goes
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 1 -> x      N of visible columns
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = 0, 1 or 2   0 = NOCASING 1 = UCASEed 2 = LCASEed
  Misc   = x           maximum field length if => Cols
  Just   = x           NOT USED
  Mask$  =             if NULL any characters accepted
                       ELSE only chars in Mask$ eg: " 0123456789-"

  DATA IN : any string value from NULL to .Misc in length
  DATA OUT: a string of up to .Col or .Misc length

  As you can see this field can and will scroll through a long string of
  data. By using Mask$ you can restrict the keyboard input to only a few
  keys. ie: "0123456789-() " works well for phone numbers

  Another property of Alpha is that it will automatically issue a CHR$(13)
  if .Cols = 1 and an acceptable key is input. This allows you to use ALPHA
  for yes/no answers and the like, but QUERY does a better job of it.

 
 BLOCK O' TEXT
 
  Style  = "B"         Block of text
  Row    = 1 -> x      top most screen row
  Col    = 1 -> x      left most screen column
  Cols   = 1 -> x      N of columns
  MustBe = NOT USED    0 is assumed
  Cased  = NOT USED    0 is assumed
  Misc   = x           maximum number of rows of .Cols chars
  Just   = NOT USED
  Mask$  = NOT USED

  DATA IN : any string of text
  DATA OUT: a string of text

  A simple word-wrap is employed SEE: fWrapPosF%
  <UP> and <DOWN> keys are used and <ENTER> acts as <DOWN>
  fTinput$ can return <UP> and <DOWN> as defaults from this function

 
 DATES
 
  Style  = "D"         Dates
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 8 or 10     8 is default ( + LEN(day$) )
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = NOT USED
  Misc   = NOT USED
  Just   = NOT USED
  Mask$  = "Sun |Mon |Tue |Wed |Thr |Fri |Sat "
                       NOTE: if Mask$ is used
                             * all day names need be the same length
                             * .Cols must include this length
                             * D$ is _NOT_ effected
                             The data field looks like "Mon 01-01-1995"
                             and the user only inputs the date not the
                             day.
 DATA IN : an 8 or 10 character date string in SetDateFormat style
 DATA OUT: same as data in

 If .MustBe > 0 and incoming D$ is NULL then the system's date is used
 ELSE
 Before the field exits the date entered must be a correct and legal date
 or a NULL field will exit with a value of SPACE$(.Cols) and display.

 The grey       "*" resets the date to the current date
                "-" -  1 day  (yesterday)  ' these keys come in handy, along
          <CTRL>"-" - 28 days ( 4 weeks)   ' with the day of the week for
          <ALT> "-" -364 days (52 weeks)   ' businesses that need to set-up
                "+" +  1 day  (yesterday)  ' appointments, meetings, etc.
          <CTRL>"+" + 28 days ( 4 weeks)   '
          <ALT> "+" +364 days (52 weeks)   '
 
 HEX & DECIMAL ASCII VALUES
 
  Style  = "H"         Hexadecimal & decimal
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 1 -> x      N of visible columns
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = NOT USED
  Misc   = x           max number of chars to be edited
  Just   = NOT USED
  Mask$  = NOT USED

  DATA IN : any string  eg: "HELLO" user sees: "48h,45h,4Ch,4Ch,4Fh"
  DATA OUT: ditto                          or: "072,069,076,076,079"

  DATA IN : is a simple string "ABC" but is displayed as "41h,42h,43h"
            during editing. The user also has the use of the <SPACE> key to
            switch between HEX and DECimal display where "ABC" would become
            "065,066,067". During editing both HEX and DECimal are accepted
            and converted afterwards. Before exiting the whole mess is
            cleaned up and values > 255 are rejected.
  DATA OUT: "ABC" format

  The total length of the field is ( ( .Misc * 4 ) - 1 ) so .Misc
    is only the individual character count and not the total space
    required to show each character in HEX and/or DECimal format

 
 MASKED
 
  Style  = "M"         Masked input
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 1 -> x      N of visible columns (Not the mask length)
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = 1           1 = UCASEed input only
  Misc   = NOT USED
  Just   = NOT USED
  Mask$  = see below   eg: "SS_A_N: NNN-NN-NNNN"

  DATA IN:  a correct data string in the prescribed format or NULL
  DATA OUT: a correct data string or NULL if .MustBe = 0

  Input is strictly controlled by use of an encoded Mask$.
  There are only six character types:
     N = mandatory (0123456789)
     n = optional  (0123456789) and <SPACE>
     A = mandatory (A -> Z)
     a = optional  (A -> Z) and <SPACE>
     E = mandatory > <SPACE>
     e = optional  anything goes!
     _   preceding N,n,A,a,E, or e prints the letter
     __  prints a single underline character

  Mask$, as shown above, would present "SSAN:    -  -    " to the user. As
  the numbers are filled in the cursor would automatically skip the "-"'s
  allowing for rapid, trouble free formatted input. The cursor can be moved
  around the field but will only land on character positions requiring
  input. Individual character deletion can also be done but, once again,
  only on characters in the "active" positions.

  Mask$ "N-NN-NNNNNN-E" would work will for ISBNs where the last position
                        "E" could be either a number or the letter "X".
  Mask$ "Nn-Nn-NNnn"    could be used for dates but a poor second to the
                        individual date style

 
 NUMERIC
 
  Style  = "N"         Numbers (as values not strings)
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 1 -> x      length of Mask$
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = NOT USED
  Misc   = NOT USED
  Just   = NOT USED
  Mask$  = "+#,###.#"  DO NOT USE "+####,.#" format!

  DATA IN:  incoming data is passed through VAL() to determine it's
            numeric value (if any)
  DATA OUT: a string suitable for conversion with VAL()

  Once again what is input is governed totally by Mask$. As shown above the
  value input can be from -9,999.99 to +9,999.99. As you can see Mask$ must
  be a valid format string suitable for use with USING$. If there is a
  leading "+" then both positive and negative numbers will be accepted else
  only positive numbers can be accepted. There seems to be a slight bug in
  v3.1 that incorrectly converts "####,.##" style masks when there are two
  or more commas involved in the output so use "#,###.##" just to be safe.

  Input for this field emulates that of a calculator; where the number
  "grows" from right to left and only the right most character can be
  erased using <DEL> or <BKSPC>. <CTRL><DEL> ZERO's the field's value.
               user presses "1"    sees:      1
               user presses "2"    sees:     12
               user presses "3"    sees:    123
               user presses "4"    sees:   1234

 
 PATH
 
  Style  = "P"         DOS Path's, file names, etc.
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 1 -> x      N of visible columns
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = 2           2 = LCASEed (UCASE is assumed)
  Misc   = x           maximum field length => Cols
  Just   = NOT USED
  Mask$  = ":\.?*"     any or all of these chars but NO OTHERS please
                       any character can be made available however

  DATA IN : any legal DOS string value from NULL to .Misc in length

  This field is, in fact, only a sub-set of the Alpha field. It adds to
  the Mask$ you send all the legal DOS characters except the 5 listed
  above. As you can see these 5 control the field's ability to input
  drive and/or path information and/or wild cards. It also defaults to
  upper case conversion unless you send an explicit value of 2 for .Cased

  Some testing is done on the input string in an attempt to forestall user
  mistakes but it's not perfect as no one can tell just what a user will do
  when given a chance ;)

 
 QUERY
 
  Style  = "Q"         Query or Questions
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 1 -> x      N of visible columns
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = NOT USED    UCASE only
  Misc   = x           maximum field length => Cols
  Just   = NOT USED
  Mask$  = see below   "MFN?|Male|Female|Not Disclosed|(Male,Female,None)"

  DATA IN : a single character
            if NULL then the left most character in Mask$ is assumed
  DATA OUT: one of the characters in the 1st field of Mask$ (except "?")

  This field acts as a mini-menu. Mask$ is cut into several pieces by the
  pipe "|" character. The first (left most) field are the legal selections
  with their long versions following in order. As the long versions are what
  the user sees .Cols must always represent the longest of the set.

  If the question mark "?" is used it must be the last selection and will
  cause the final field to be displayed in the event the user presses an
  illegal key. It can not be returned as an answer.

  Using Mask$ as we have it above:
  The user may press "M", "F", or "N" to get an acceptable answer and
  immediate exit from the field. Assuming the "M" is pressed the string
  "Male" will be printed on the screen. If, however, the "?" were pressed
  then "(Male, Female or None)" would be displayed for 2 seconds and would
  either exit upon a valid key or revert back to the original value if the
  time elapsed first.

  NOTE: an approximation of this process can be duplicated by using the
        Alpha field with .Cols  = 1
                         .Cased = 1
                          Mask$ = "MFN"

  HINT: if you want your answers printed with color commands (like the
        first letter blue and the rest grey) use a color attribute value
        of ZERO for the normal attribute then embed the color command
        in the answer pieces

 
 TIMES
 
  Style  = "T"         Times
  Row    = 1 -> x      screen row
  Col    = 1 -> x      left most screen column
  Cols   = 6 or 9      6 is default
  MustBe = 0 or x      > 0 if field is mandatory
  Cased  = NOT USED
  Misc   = NOT USED
  Just   = NOT USED
  Mask$  = NOT USED


 DATA  IN: a 6 or 9 char time string  eg: "HH:MM " or "HH:MM:SSx"
 DATA OUT: same format as data in          SEE: SetTimeFormat

 if .MustBe > 0 and incoming D$ is NULL then the system's time will
 be automatically loaded
 input style is dependent upon the values in SetTimeFormat

 NOTE: times are ALWAYS edited as 24 hour clocks avoiding the "a" and "p"

 
                                                                    
 
 fTinput$  ( {D$}, Control$, ExitKeys$, Nattr?, Hattr? )
 fTinputM$ ( {D$}, Control$, ExitKeys$, Nattr?, Hattr?, HotSpot% )
   D$        is both the incoming and exiting data
             if NULL then incoming data has failed some internal test
                     or has a value of ZERO for "N"umbers
   Control$  is a construct of ( TinputTYPE + Mask$ )
             just a quick word here just in case it isn't quite clear what
             we are looking for:
             TinputTYPE is used to convert the first 8 characters of
             Control$ into the separate members while Mask$ is actually
             cut out of the middle. LSET tINP  = LEFT$( Control$, 8 )
                                         Mask$ =  MID$( Control$, 9 )
             The reason this is done is to simplify storage of control
             strings in data files. You can, of course, use the TYPE in
             your code too.
   ExitKeys$ has 2 default keys loaded by the function:
             CHR$(13) <ENTER>  = acceptance of the data
             CHR$(27) <ESC>    = disregard changes/restore original value
             You may add any other key(s) you wish.
             F-1 and F-10 would be ExitKeys$ = CHR$(0,59,0,68)
   Nattr?    is the color attribute to use upon exiting the field
   Hattr?    is the color attribute to use while the field is being edited
   HotSpot%  the item number for the field being edited

   fTinput$  RETURNS CHR$(?)   exiting key-press
   fTinputM$ RETURNS CHR$(?)   exiting key-press
                     NULL      mouse clicked in another item of current event
                     CHR$(255) mouse clicked in another event
 
 The following keys are default unless also found in ExitKeys$

 <BkSpc>       delete character to the left of the cursor
 <HOME>        cursor to #1 character position
 <LEFT>        move cursor 1 position left
 <RIGHT>       move cursor 1 position right
 <END>         cursor to end of data or last character position
 <INS>         toggle insert ON/OFF   SEE: fINSERTmsg%
 <DEL>         delete character under the cursor
 <CTRL><LEFT>  next word left
 <CTRL><RIGHT> next word right
 <CTRL><DEL>   erase all data in the field

 <ENTER>       exit field, accept new data
 <ESC>         exit field, keep original data

 NOTE: some of these keys have no effect on some of the field styles.
       ( Logic dictates here. ie: CTRL/LEFT and Numerics isn't a match )
       If a key cannot be used by any field style it is simply ignored.

 Even though each field can handle incoming NULL values for the data  I've
 found it best to supply whatever defaults your program may require.  Even
 on the odd chance that the style's idea of a default value is the same as
 the program's there is no guarantee  that the user will pass through that
 particular field. This would leave your data "out of shape" and may cause
 problems in other portions of the program. It is much safer to <KNOW> the
 data is in the proper format throughout the program than to be constantly
 checking it. ( Saves on the Raid bill, too... :)


                   putting it all together                        


 So far I've only been talking about single fields and that, no matter how
 good it is, is only the tip of the iceberg. Greater things are afoot here
 and can now be addressed.

 More times than not there is more that one field of data that needs to
 be input. One medical program I built years ago had over 2,500 fields for
 each record spread over 40 different screens, and without a consolidated
 screen input routine I would still be writing code for them all. Enter
 the arrays! Now no matter how many fields your screens may have, you can
 simply pack the data and control strings for each field into arrays and
 put the whole process into a single loop. By loading ExitKeys$ with <UP>,
 <DOWN>, <F-10>, etc. you can easily control movement within the array.
 Then (and you thought I forgot) by using the .Just member of the TYPE
 your loop can be made to perform field specific checking, rejection and
 message warnings, or any one of a dozen other tasks quite simply!
 SEE: fJustify$

 As it is a very real possibility that not all programs will require all
 the field styles, each style is split into a separate .INC file.
 TINPUT.UNT has 9 integer constants at the beginning of the file that will
 either include or exclude each of the TINPUT_?.INC files. This allows you
 to keep your programs down to their proper size without a batch of useless
 code laying around.

 The following files are required:
   TINPUT  .TYP     ' the type declaration
   TINPUT  .UNT     ' fTinput$ and all the supporting routines
   TINPUTM .UNT     ' fTinputM$ and it's supporting routines (Mouse Aware)
   TINPUT_A.INC     ' Alpha input
   TINPUT_B.INC     ' Blocks
   TINPUT_D.INC     ' Dates         ( requires TINPUT_M.INC )
   TINPUT_H.INC     ' Hex & Decimal ( requires TINPUT_A.INC )
   TINPUT_M.INC     ' Masked
   TINPUT_N.INC     ' Numbers
   TINPUT_P.INC     ' Path          ( requires TINPUT_A.INC )
   TINPUT_Q.INC     ' Query
   TINPUT_T.INC     ' Times         ( requires TINPUT_M.INC )

 fTinput2$ ( SEG D$(), SEG F$(), SEG T?(), SEG H$(), SEG Fld% )
   D$() all the incoming/outgoing data for the fields
   F$() all the control strings
   T?() if UBOUND(T?(1)) > 0 then the field numbers to be tabbed to
        the field numbers must be in ascending order
   H$() if UBOUND(H$(1)) > 0 then the help strings are printed by
        calling fHelpLine$
   Fld% the field to start processing at

   This routine is ready to be input directly into your programs, but you
   will probably want to modify it to fit your exact needs. The "tab fields"
   is a handy gizmo if your program uses some monster input screens
   but really does very little on the small stuff and would just be a
   nuisance. Rip it out! Ditto with the help line. I like it (obviously)
   and use it constantly but.....
   There is no reason that you couldn't compile it and stuff it in the
   .PBL but I don't think I've every written 2 programs that were enough
   alike to do that and that is why it's an .INC file.

   The last little part is the use of fJustify$. The individual field
   routines are pretty smart to start with and you may find that fJustify$
   just gets in the way. No problemo... <CTRL>Y is at your beck n' call!

 In TINPUT.DMO I've hung the whole thing together so you could see it in
 action, and play with it.

 fJustify$ is also, finally, put to some practical use in this file. It
 really doesn't have a lot of use in the demo but I'm sure you will get
 the general idea. It is not inconceivable that you could construct yet
 another function ( fPreJust$ ) to load default values! Two old saws come
 to mind:
                  "Never write the same code twice."
                  "There are exceptions to every rule."

  Last minute entry:
    fQueryPrint$ ( Row?, Col?, Cols?, Mask$, Answer$ )
    This one was added as a good afterthought to all of this. If you
    remember the Query field you know that the incoming/outgoing data is
    a single letter that is found in the first piece of Mask$ and that the
    first answer is default if Answer$ is NULL.
                       Mask$ = "YNM|Yes|No|Maybe"
    fQueryPrint$ will return the correct piece of Mask$ and if Row? > ZERO
    will use TprintCLEAR with an attribute of ZERO to print the string.
    While writing a small, small program for a local business I just got
    tired of doing the 2 or 3 lines of code necessary to make all this
    happen, so.......

 See you in TINPUT.DMO!
