home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TOTDOC.ZIP / CHAPT20.TXT < prev    next >
Encoding:
Text File  |  1991-02-11  |  34.1 KB  |  799 lines

  1.                                                                        Extending
  2.                                                                            Input
  3.                                                                            Field
  4.                                                                            Types
  5.  
  6.  
  7.  
  8.  
  9.  
  10.          "Stay humble. Always answer the phone - no matter who else is in the
  11.          car."
  12.  
  13.                                                                      Jack Lemmon
  14.  
  15.  
  16.  
  17.          One of the most used elements of the Toolkit is the full screen input
  18.          facility. If the Toolkit field types do not meet your exact needs, you
  19.          can create your own custom field types. This chapter explains how.
  20.  
  21.  
  22.  
  23. The Input Object Hierarchy
  24.  
  25.          The objects FormOBJ and WinFormOBJ are used to manage and control full
  26.          screen input. You may recall that the method AddItem is used to add
  27.          individual input fields to the form. AddItem accepts any of the input
  28.          fields shown in the TOTIO Object Hierarchy on page 11.5. As the diagram
  29.          illustrates, all IO objects are descended from the root object Base-
  30.          IOOBJ. If you want to create new input field objects which can be
  31.          managed by the form objects, the new objects must be descended from
  32.          BaseIOOBJ, or any object descended from BaseIOOBJ.
  33.  
  34.          The BaseIOOBJ object includes the following data and methods, which are
  35.          inherited by all descendant objects:
  36.  
  37.          ItemIOOBJ = object
  38.             vBoundary: tCoords;
  39.             vHotKey: word;
  40.             vID: word;
  41.             vActive: boolean;
  42.             {methods ...}
  43.             constructor Init;
  44.             procedure   SetActiveStatus(Selectable:boolean);
  45.             function    Active:boolean;
  46.             function    GetHotKey: word;
  47.             procedure   SetHotkey(HK:word);
  48.             function    GetID: word;
  49.             procedure   SetID(ID:word);
  50.             function    Visible: boolean;                        VIRTUAL;
  51.             procedure   Display(Status:tStatus);                 VIRTUAL;
  52.             function    Select(K:word; X,Y:byte):tAction;        VIRTUAL;
  53.             function    ProcessKey(InKey:word;X,Y:byte):tAction; VIRTUAL;
  54.             function    Suspend:boolean;                         VIRTUAL;
  55.             destructor  Done;                                    VIRTUAL;
  56.          end; {ItemIOOBJ}
  57.  
  58.          Note: the BaseIOOBJ also includes signal-related methods. These are
  59.          discussed in a later section.
  60.  
  61. 20-2                                                       Extending the Toolkit
  62.  
  63. --------------------------------------------------------------------------------
  64.  
  65.          The vBoundary variable identifies the (X1,Y1) and (X2,Y2) coordinates
  66.          of the field. When the user clicks the left mouse button during full-
  67.          form input, the form object scans the list of active input objects and
  68.          moves the user to the input object with coordinates corresponding to
  69.          the mouse cursor position. Any descendant field should therefore update
  70.          the vBoundary variable to indicate the physical location of the field.
  71.          The other three BaseIOOBJ variables identify the field's hotkey, ID and
  72.          whether the field is active or selectable. These variables are managed
  73.          by the BaseIOOBJ methods SetActiveStatus, Active, GetHotkey, SetHotkey,
  74.          SetID and GetID. All these methods are suitable for any field type, and
  75.          should not need modification in descendant objects. Just inherit them
  76.          and use them!
  77.  
  78.          The virtual methods, highlighted in bold, are specific to each descen-
  79.          dant object. As a minimum, any descendant objects should redefine these
  80.          bold methods -- they are the main methods called by the form object
  81.          during full-screen input.
  82.  
  83.          Apart from special hotkeys and navigation control keys, all the user
  84.          input fields are visible. That is, the user can see them. As the TOTIO
  85.          Hierarchy Diagram illustrates, all visible fields are descended from
  86.          VisibleIOOBJ, which is, in turn, descended from BaseIOOBJ. In addition
  87.          to the BaseIOOBJ objects just discussed, the VisibleIOOBJ objects
  88.          inherit the following methods:
  89.  
  90.            procedure   SetLabel(Lbl:string);
  91.            procedure   SetMessage(X,Y:byte; Msg:string);
  92.            procedure   WriteMessage;
  93.            procedure   WriteLabel(Status:tStatus);               VIRTUAL;
  94.  
  95.          As their names suggest, these methods are used to set and display
  96.          labels and messages. Labels are displayed to the immediate left of a
  97.          field and act as a field title. A message is the field's descriptive
  98.          text which is displayed when the user moves to the field. Under normal
  99.          circumstances you will not need to modify these methods. They are
  100.          appropriate to any field type.
  101.  
  102.  
  103.  
  104. Creating New Field Types
  105.  
  106.          When you want to create a new field object, you must decide which
  107.          existing field object has the properties most closely resembling the
  108.          new field type you want to create. For example, if the field includes
  109.          data input, then you would probably create a descendant of CharIOOBJ.
  110.          However, if the field has multiple lines (like a radio button or list),
  111.          then the new object would best be a descendant of MultiLineIOOBJ. If
  112.          none of the existing fields come anywhere close, create a descendant
  113.          from VisibleIOOBJ.
  114.  
  115.  
  116.  
  117. Extending Input Field Types                                                 20-3
  118.  
  119. --------------------------------------------------------------------------------
  120.  
  121.          To illustrate the principles, a new boolean object will be created.
  122.          This object will display two different options, e.g. Yes or No, True or
  123.          False, Live or Die, etc. The field will display one of the options, and
  124.          when the user presses the space bar or clicks the mouse, the field will
  125.          flip to the other option.
  126.  
  127.          Since the boolean object does not process individual character input,
  128.          and does not occupy multiple lines, the best object to descend from is
  129.          VisibleIOOBJ. The following methods are inherited from VisibleIOOBJ and
  130.          do not need to be modified:
  131.  
  132.             procedure   SetActiveStatus(Selectable:boolean);
  133.             function    Active:boolean;
  134.             function    GetHotKey: word;
  135.             procedure   SetHotkey(HK:word);
  136.             function    GetID: word;
  137.             procedure   SetID(ID:word);
  138.             procedure   SetLabel(Lbl:string);
  139.             procedure   SetMessage(X,Y:byte; Msg:string);
  140.             procedure   WriteMessage;
  141.             procedure   WriteLabel(Status:tStatus);              VIRTUAL;
  142.             function    Visible: boolean;                        VIRTUAL;
  143.  
  144.          As well as replacing Init and Done, the primary inherited methods which
  145.          need to be over-written are Display, Select, Processkey and Suspend.
  146.          These four methods are called by the form object during full screen
  147.          input. Additionally, if you want the new boolean object to function
  148.          stand-alone, i.e. without being part of a form, an Activate method
  149.          should be added. Activate will display the field and process user input
  150.          until [KEYCAP] or [KEYCAP] is pressed.
  151.  
  152.          In keeping with the Toolkit style, SetValue and GetValue methods should
  153.          also be added. These methods are used to set the object's default
  154.          value, i.e. which option to display when the field is activated, and to
  155.          get the user-selected value after input is complete.
  156.  
  157.          The new boolean object will need to include three data variables: the
  158.          two strings that represent the true and false settings, and a boolean
  159.          to record the object's actual value.
  160.  
  161.          After all the methods and data have been included, the definition of
  162.          the new BooleanIOOBJ is as follows:
  163.  
  164.          BooleanIOOBJ = object (VisibleIOOBJ)
  165.             OnString: StringBut;
  166.             OffString: StringBut;
  167.             vInput: boolean;
  168.             {methods...}
  169.             Constructor Init(X,Y:byte; Yes,No:stringbut);
  170.             function    GetValue: boolean;
  171.             procedure   SetValue(On:boolean);
  172.             procedure   Activate;
  173.  
  174.  
  175.  
  176. 20-4                                                       Extending the Toolkit
  177.  
  178. --------------------------------------------------------------------------------
  179.  
  180.             procedure   Display(Status:tStatus);                  VIRTUAL;
  181.             function    Select(K:word; X,Y:byte):tAction;         VIRTUAL;
  182.             function    ProcessKey(InKey:word;X,Y:byte):tAction;  VIRTUAL;
  183.             function    Suspend:boolean;                          VIRTUAL;
  184.             destructor  Done;                                     VIRTUAL;
  185.          end; {BooleanIOOBJ}
  186.  
  187.  
  188.          In the following sections, each method is individually discussed:
  189.  
  190.  
  191.  
  192. Extending Input Field Types                                                 20-5
  193.  
  194. --------------------------------------------------------------------------------
  195.  
  196. Init
  197.  
  198.          The primary responsibilities of the Init method are to set the values
  199.          of the true and false strings, and to update the vBoundary variable
  200.          with the location of the field. In keeping with the other input fields,
  201.          the Init method is passed the (X,Y) coordinate of the leftmost charac-
  202.          ter. By finding the length of the longest string, the method can com-
  203.          pute the rightmost (X,Y) coordinate. The method detail is, therefore,
  204.          as follows:
  205.  
  206.          constructor BooleanIOOBJ.Init(X,Y:byte; Yes,No:stringbut);
  207.          {}
  208.          var L:byte;
  209.          begin
  210.             VisibleIOOBJ.Init;
  211.             OnString := Yes;
  212.             OffString := No;
  213.             L := length(OnString);
  214.             if L < length(OffString) then
  215.                L := length(OffString);
  216.             with vBoundary do
  217.             begin
  218.                X1 := X;
  219.                X2 := X + pred(L);
  220.                Y1 := Y;
  221.                Y2 := Y;
  222.             end;
  223.             vInput := true;
  224.          end; {BooleanIOOBJ.Init}
  225.  
  226.  
  227.  
  228. SetValue and GetValue
  229.  
  230.          These methods are short and to the point. All they do is set or return
  231.          the value of the field, and are defined as follows:
  232.  
  233.          function BooleanIOOBJ.GetValue: boolean;
  234.          {}
  235.          begin
  236.             GetValue := vInput;
  237.          end; {BooleanIOOBJ.GetValue;
  238.  
  239.          procedure BooleanIOOBJ.SetValue(On:boolean);
  240.          {}
  241.          begin
  242.             vInput := On;
  243.          end; {BooleanIOOBJ.SetValue}
  244.  
  245.  
  246.  
  247.  
  248. 20-6                                                       Extending the Toolkit
  249.  
  250. --------------------------------------------------------------------------------
  251.  
  252. Display
  253.  
  254.          Display is a virtual method which must be declared with a single passed
  255.          parameter of type tStatus. tStatus is an enumerated type with the mem-
  256.          bers HiStatus, Norm and Off, and the value is used to indicate whether
  257.          the field is highlighted (the field the user is currently editing),
  258.          normal (one of the other fields in a form), or inactive (cannot be
  259.          selected).
  260.  
  261.          The primary responsibility of Display is to display the field contents
  262.          in the appropriate color. The first task is to decide the display
  263.          attribute. To be consistent with the other input objects, the field
  264.          should ascertain the attribute by calling a TOTIO^ function method.
  265.          TOTIO controls the colors for field labels and messages, button fields,
  266.          group fields, list fields, and single line fields. In this case, the
  267.          single line field colors are appropriate. Refer to page 11-40 for a
  268.          full discussion of IOTOT.
  269.  
  270.          The BooleanIOOBJ method Display is implemented as follows:
  271.  
  272.          procedure BooleanIOOBJ.Display(Status:tStatus);
  273.          {}
  274.          var Att: byte;
  275.          begin
  276.             case Status of
  277.                HiStatus: Att := IOTOT^.FieldCol(2);
  278.                Norm:     Att := IOTOT^.FieldCol(1);
  279.                Off:      Att := IOTOT^.FieldCol(4);
  280.             end; {case}
  281.             with vBoundary do
  282.                if vInput then
  283.                   Screen.WriteAT(X1,Y1,Att,padleft(OnString,succ(X2-X1),' '))
  284.                else
  285.                   Screen.WriteAT(X1,Y1,Att,padleft(OffString,succ(X2-X1),' '));
  286.          end; {BooleanIOOBJ.Display}
  287.  
  288.  
  289.  
  290. Select
  291.  
  292.          The Select method is called by the form object whenever the user tries
  293.          to enter the field. The method is responsible for displaying the field
  294.          contents as well as the field's label and message. Select is also
  295.          responsible for moving the cursor to the field.
  296.  
  297.          Select is actually a function method which returns a member of the
  298.          enumerated type tAction. tAction is used to instruct the form object on
  299.          how to proceed, and includes the members None, NextField, PrevField,
  300.          Finished, Escaped, Refresh, Signal, Enter, Help, Stop1..Stop9. Under
  301.          normal circumstances, Select should return a value of None. This
  302.  
  303.  
  304.  
  305.  
  306. Extending Input Field Types                                                 20-7
  307.  
  308. --------------------------------------------------------------------------------
  309.  
  310.          instructs the Toolkit to proceed as normal. The majority of the other
  311.          members are used by "buttons", which the user selects to finish the
  312.          input session or to ask for help.
  313.  
  314.          The BooleanIOOBJ method Select is implemented as follows:
  315.  
  316.          function BooleanIOOBJ.Select(K:word; X,Y:byte):tAction;
  317.          {}
  318.          begin
  319.             Display(HiStatus);
  320.             WriteLabel(HiStatus);
  321.             WriteMessage;
  322.             Screen.GotoXY(vBoundary.X1,vBoundary.Y1);
  323.             Select := none;
  324.          end; {BooleanIOOBJ.Select}
  325.  
  326.  
  327.  
  328. ProcessKey
  329.  
  330.          When a field is active, the form object repeatedly passes the user
  331.          input to the highlighted object. This continues until the user moves to
  332.          another field, or presses a key which indicates the user wants to fin-
  333.          ish the input session.
  334.  
  335.          The form object calls the field's method ProcessKey and passes the user
  336.          input to it. The ProcessKey method then updates the value of the field
  337.          based on the user's input. In the case of the BooleanIOOBJ field, the
  338.          field will flip to the other string whenever the keys [KEYCAP] [KEYCAP]
  339.          or [KEYCAP] are pressed. The field will also flip if the mouse is
  340.          clicked on the field. The Toolkit responds extremely quickly to a mouse
  341.          press, and it is a good idea to delay for a tenth of a second when the
  342.          mouse has been clicked. This overcomes the problem of the field flip-
  343.          ping a dozen or more times each time the user clicks the mouse.
  344.  
  345.          ProcessKey is a function method which returns a member of the enumer-
  346.          ated type tAction. Under normal circumstances, the function should
  347.          return a value of None, which indicates that the form object should
  348.          continue passing keys to the field.
  349.  
  350.  
  351.  
  352.  
  353.            Note: if you were creating a different input field type, you might
  354.            want to return a value of NextField when the current field became
  355.            full. This instructs the form object to suspend the current field
  356.            and select the next field.
  357.  
  358.  
  359.  
  360.  
  361.          The BooleanIOOBJ method ProcessKey is implemented as follows:
  362.  
  363.  
  364.  
  365.  
  366. 20-8                                                       Extending the Toolkit
  367.  
  368. --------------------------------------------------------------------------------
  369.  
  370.          function BooleanIOOBJ.ProcessKey(InKey:word;X,Y:byte):tAction;
  371.          {}
  372.          begin
  373.             if (InKey = 513)
  374.             or (InKey = 32)
  375.             or (inKey = 328)
  376.             or (InKey = 336) then
  377.             begin
  378.                vInput := not vInput;
  379.                Display(HiStatus);
  380.             end;
  381.             if InKey = 513 then {absorb mouse}
  382.                delay(100);
  383.             ProcessKey := None;
  384.          end; {BooleanIOOBJ.ProcessKey}
  385.  
  386.  
  387.  
  388. Suspend
  389.  
  390.          The Suspend method is called by the form object when the user wants to
  391.          terminate input or move to another field. Suspend is responsible for
  392.          displaying the field and label in the normal attribute, and for remov-
  393.          ing the field message. All these tasks are performed by the inherited
  394.          VisibleIOOBJ method Suspend.
  395.  
  396.          Suspend is actually a function method which returns a boolean value.
  397.          This provides a mechanism for not allowing the user to leave the field.
  398.          If Suspend returns False, the form object stays in the field. This
  399.          facility should only be used when the user has entered some invalid
  400.          input, and it is good practice to display a message stating how the
  401.          user can correct the problem.
  402.  
  403.          A user cannot enter an invalid value in a BooleanIOOBJ, and so Suspend
  404.          will always return True. The method Suspend is implemented as follows:
  405.  
  406.          function BooleanIOOBJ.Suspend:boolean;
  407.          {}
  408.          begin
  409.             Suspend := VisibleIOOBJ.Suspend;
  410.          end; {BooleanIOOBJ.Suspend}
  411.  
  412.  
  413.  
  414. Activate
  415.  
  416.          The Activate method provides a way to use the object as a stand-alone
  417.          field, i.e. not as part of a form. Activate is responsible for select-
  418.          ing the field, getting input, and passing the input to ProcessKey.
  419.          Activate should repeatedly pass input to ProcessKey until the user
  420.          presses [KEYCAP] or [KEYCAP] to indicate the end of input. The method
  421.          Suspend should then be called to deselect the field.
  422.  
  423.  
  424.  
  425. Extending Input Field Types                                                 20-9
  426.  
  427. --------------------------------------------------------------------------------
  428.  
  429.          The BooleanIOOBJ method Activate is implemented as follows:
  430.  
  431.          procedure BooleanIOOBJ.Activate;
  432.          {}
  433.          var
  434.             Action: tAction;
  435.          begin
  436.             Action := Select(0,0,0);
  437.             with Key do
  438.             begin
  439.                repeat
  440.                   GetInput;
  441.                   Action := ProcessKey(LastKey,LastX,LastY);
  442.                until ((LastKey = 324) or (LastKey = 13)) and Suspend;
  443.             end;
  444.          end; {BooleanIOOBJ.Activate}
  445.  
  446.  
  447.  
  448.  
  449. Done
  450.  
  451.          Since BooleanIOOBJ has no dynamic data of its own, all Done needs to do
  452.          is call VisibleIOOBJ's method Done, as follows:
  453.  
  454.          destructor BooleanIOOBJ.Done;
  455.          {}
  456.          begin
  457.             VisibleIOOBJ.Done;
  458.          end; {BooleanIOOBJ.Done}
  459.  
  460.  
  461.  
  462.  
  463.  
  464.          That's the new BooleanIOOBJ defined. The full source code is contained
  465.          in the file EXTIO.PAS.
  466.  
  467.  
  468.  
  469. Using BooleanIOOBJ
  470.  
  471.          Since BooleanIOOBJ is inherited from BaseIOOBJ, it can be used in full
  472.          form input just like any other input object. Listed below is the demo
  473.          program EXTDEM7.PAS which shows the new object in action. This demo
  474.          program is actually an adaptation of DEMIO2.PAS discussed in chapter
  475.          11. Figure 20.1 illustrates the generated display.
  476.  
  477.          Program ExtendedDemoSeven;
  478.  
  479.          Uses DOS,CRT,
  480.               totFAST, totIO1, totIO2, extIO;
  481.  
  482.  
  483.  
  484.  
  485. 20-10                                                      Extending the Toolkit
  486.  
  487. --------------------------------------------------------------------------------
  488.  
  489.          Var
  490.             Name: LateralIOOBJ;
  491.             Phone: PictureIOOBJ;
  492.             Price: FixedRealIOOBJ;
  493.             Status: BooleanIOOBJ;
  494.             Keys: ControlkeysIOOBJ;
  495.             Manager: FormOBJ;
  496.             Result: tAction;
  497.  
  498.          procedure InitVars;
  499.          {}
  500.          begin
  501.             with Name do
  502.             begin
  503.                Init(35,5,20,40);
  504.                SetLabel('Vendor Name');
  505.             end;
  506.             with Phone do
  507.             begin
  508.                Init(35,7,'(###) ###-####');
  509.                SetLabel('Tel');
  510.                SetRules(JumpIfFull);
  511.             end;
  512.             with Price do
  513.             begin
  514.                Init(35,9,8,2);
  515.                SetLabel('Unit Price');
  516.                SetValue(250.0);
  517.                SetMinMax(0.1,12250.0);
  518.                SetRules(EraseDefault);
  519.             end;
  520.             with Status do
  521.             begin
  522.                Init(35,11,' Nice Guy ',' Jerk ');
  523.                SetLabel('Category');
  524.             end;
  525.             Keys.Init;
  526.          end; {InitVars}
  527.  
  528.          begin
  529.             ClrScr;
  530.             Screen.TitledBox(15,3,65,13,76,79,78,2,' Quicky Input Demo ');
  531.             Screen.WriteCenter(25,white,'Press TAB to switch fields
  532.                                          and press ESC or F10 to end');
  533.             InitVars;
  534.             with Manager do
  535.             begin
  536.                Init;
  537.                AddItem(Keys);
  538.  
  539.  
  540.  
  541.  
  542. Extending Input Field Types                                                20-11
  543.  
  544. --------------------------------------------------------------------------------
  545.  
  546.                AddItem(Name);
  547.                AddItem(Phone);
  548.                AddItem(Price);
  549.                AddItem(Status);
  550.                Result := Go;
  551.                if Result = Finished then
  552.                   {update the database..}
  553.                else
  554.                   {call Esc routine};
  555.             end;
  556.          end.
  557.  
  558.  
  559.  
  560.  
  561. Figure 20.1                                                             [SCREEN]
  562. Using
  563. BooleanIOOBJ
  564.  
  565.  
  566.  
  567. Understanding Signals
  568.  
  569.          In sophisticated input forms, the data input by a user in one field may
  570.          affect the data of some related fields on the form. Signals provide
  571.          this capability in the Toolkit.
  572.  
  573.  
  574.  
  575. Signal Theory
  576.  
  577.          The BaseIOOBJ object includes the following three signal-related meth-
  578.          ods:
  579.  
  580.          procedure   RaiseSignal(var TheSig:tSignal);                       VIR-
  581.          TUAL;
  582.          procedure   ShutdownSignal(var BaseSig:tSignal);
  583.          VIRTUAL;
  584.          procedure   HandleSignal(var BaseSig:tSignal; var NewSig:tSignal); VIR-
  585.          TUAL;
  586.  
  587.          The totIO1 unit includes the record tSignal, which is defined as fol-
  588.          lows:
  589.  
  590.          tSignal = record
  591.             ID: word;
  592.             MsgType: word;
  593.             case word of           {variant record}
  594.             0: (MsgPtr: pointer);
  595.             1: (MsgLong: longint);
  596.             2: (MsgWord: word);
  597.             3: (MsgInt: integer);
  598.  
  599.  
  600.  
  601.  
  602. 20-12                                                      Extending the Toolkit
  603.  
  604. --------------------------------------------------------------------------------
  605.  
  606.             4: (MsgByte: byte);
  607.             5: (MsgChar: char);
  608.          end;
  609.  
  610.          tSignal is a variant record which can be used to store any data which
  611.          needs to be communicated between input fields.
  612.  
  613.          An input field's object methods Select, ProcessKey, and ProcessEnter
  614.          are function methods which return a value of type tAction. If any of
  615.          these methods return a value of SIGNAL, the form object will immedi-
  616.          ately call that input object's RaiseSignal method. This method is
  617.          passed a variable parameter of type tSignal. The variable is updated
  618.          with the information which needs to be passed to other fields. Each
  619.          signal has an ID, and the ID should be assigned a non-zero value. In a
  620.          situation where more than one signal can be raised, this ID will indi-
  621.          cate to the other fields which signal is being raised. The signal's
  622.          MsgType field can be used to provide further information about the
  623.          signal, and usually indicates the format of the data being passed with
  624.          the signal, e.g. a 1 might indicate a longint, a 2 might indicate a
  625.          word, etc.
  626.  
  627.  
  628.  
  629.  
  630.            Note: Input objects which are descended from CharIOOBJ inherit the
  631.            virtual function method ProcessEnter. This method is passed no
  632.            parameters, and returns a value of type tAction. The method is
  633.            called whenever the user presses [KEYCAP]. It is typically used to
  634.            raise a signal or move the user to the next field.
  635.  
  636.  
  637.  
  638.  
  639.          When a field raises a signal, the form manager passes the signal to the
  640.          next field in the form. This is achieved by calling the next field's
  641.          method HandleSignal. This method is passed the tSignal variable raised
  642.          by the originating field. The HandleSignal method can inspect the sig-
  643.          nal ID and, if appropriate, respond to the signal. If the field han-
  644.          dling the signal knows that the signal is not intended for any other
  645.          field, it can update the signal ID with a value of 0. This tells the
  646.          form object that the signal has been handled, and the signal is dis-
  647.          carded. Otherwise, the signal is passed to each input field in turn
  648.          until one of the fields sets the ID to 0, or until all the fields have
  649.          been passed the signal.
  650.  
  651.          When the signal has been handled or passed to every other field, the
  652.          originating field's ShutdownSignal method is called. This method can be
  653.          used to dispose of any data that was created specifically for the sig-
  654.          nal, and for any other housekeeping.
  655.  
  656.  
  657.  
  658.  
  659. Extending Input Field Types                                                20-13
  660.  
  661. --------------------------------------------------------------------------------
  662.  
  663.          Any object which handles a signal can optionally raise a signal of its
  664.          own. The HandleSignal method is passed two parameters of type tSignal.
  665.          The first parameter is the original signal raised by another field. The
  666.          second parameter is an empty signal which the handling field can update
  667.          with its own signal. The form object inspects the second signal
  668.          returned by the field's HandleSignal method, and if the ID is set to a
  669.          non-zero value, a new signal is raised and passed to the other fields.
  670.          Only when this new signal has been handled will the form manager con-
  671.          tinue with the processing of the original signal.
  672.  
  673.  
  674.  
  675. A Signal Example
  676.  
  677.          The way to use signals is best illustrated by example. In this section
  678.          a demo program will be developed which prompts the user to input some
  679.          directories, as a precursor to installing some software. The user is to
  680.          be prompted to input five different directories, one for the programs,
  681.          one for the doc files, etc. Like Turbo Pascal's own Install program,
  682.          each of the input fields needs to be updated if the user enters a new
  683.          default directory into the first field.
  684.  
  685.          To solve this problem, two new field objects must be created, and both
  686.          of them will be descended from StringIOOBJ. One object will be used to
  687.          prompt the user to input the default directory, and will raise a signal
  688.          when the user changes the field value. The other object will be used
  689.          for the input of the other directories, and will include a method to
  690.          handle the signal raised by the first object.
  691.  
  692.          In this example, the first object is called MasterStringIOOBJ, and it
  693.          will raise a signal whenever the user enters a new directory. The new
  694.          object is declared as follows:
  695.  
  696.          TYPE
  697.          MasterStringIOOBJ = object (StringIOOBJ)
  698.             vLastInput: string;
  699.             {methods}
  700.             constructor Init(X,Y,FieldLen: byte);
  701.             function    ProcessEnter: tAction;                    VIRTUAL;
  702.             function    Select(K:word; X,Y:byte): tAction;        VIRTUAL;
  703.             procedure   RaiseSignal(var TheSig:tSignal);          VIRTUAL;
  704.             procedure   ShutdownSignal(var BaseSig:tSignal);      VIRTUAL;
  705.             function    Suspend:boolean;                          VIRTUAL;
  706.             destructor  Done;                                     VIRTUAL;
  707.          end; {MasterStringIOOBJ}
  708.  
  709.  
  710.          The new object should only raise a signal when the user has changed the
  711.          value of the field. The new string variable vLastInput is used to
  712.          record the value of the string when the field is selected. The value of
  713.  
  714.  
  715.  
  716.  
  717. 20-14                                                      Extending the Toolkit
  718.  
  719. --------------------------------------------------------------------------------
  720.  
  721.          vLastInput can then be compared to vInputStr (the edited field value)
  722.          when the user tries to leave the field or presses [KEYCAP]. The method
  723.          Select is therefore declared as follows:
  724.  
  725.          function MasterStringIOOBJ.Select(K:word; X,Y:byte): tAction;
  726.          {}
  727.          begin
  728.             vLastInput := vInputStr;
  729.             Select := StringIOOBJ.Select(K,X,Y);
  730.          end; {MasterStringIOOBJ.Select}
  731.  
  732.  
  733.          The object needs to raise a signal when the user presses [KEYCAP]. The
  734.          method ProcessEnter is implemented as follows:
  735.  
  736.          function MasterStringIOOBJ.ProcessEnter: tAction;
  737.          {}
  738.          begin
  739.             if vLastInput <> vInputStr then {need to signal}
  740.                ProcessEnter := Signal
  741.             else
  742.                ProcessEnter := none;
  743.          end; {MasterStringIOOBJ.ProcessEnter}
  744.  
  745.          If the value of the string has changed, SIGNAL is returned, otherwise
  746.          NONE is returned.
  747.  
  748.          The object also needs to raise a signal when the method Suspend is
  749.          called, and the value of the field has changed. Now we are faced with a
  750.          problem, because Suspend cannot directly raise a signal. Suspend
  751.          returns a boolean value to indicate whether the field can be suspended,
  752.          not a tAction value. The trick is to return a boolean value of False,
  753.          indicating that the user may not leave the field, and then stuff the
  754.          keyboard with the keystrokes [KEYCAP] [KEYCAP]. The Toolkit will not
  755.          allow the user to leave the field, the [KEYCAP] key will then be pro-
  756.          cessed, thereby raising a signal via the ProcessEnter method, and
  757.          finally, the [KEYCAP] key will be processed to move the user to the
  758.          next field. The Suspend method is implemented as follows:
  759.  
  760.          function MasterStringIOOBJ.Suspend:boolean;
  761.          {}
  762.          begin
  763.             if vLastInput <> vInputStr then {need to signal}
  764.             begin
  765.                Suspend := false;
  766.                Key.StuffBuffer(13); {Enter}
  767.                Key.StuffBuffer(9);   {Tab}
  768.             end
  769.             else
  770.                Suspend := StringIOOBJ.Suspend;
  771.          end; {MasterStringIOOBJ.Suspend}
  772.  
  773.  
  774.  
  775.  
  776. Extending Input Field Types                                                20-15
  777.  
  778. --------------------------------------------------------------------------------
  779.  
  780.          The RaiseSignal method must update the signal variable with the infor-
  781.          mation required by the other fields, i.e. the string representing the
  782.          new directory entered by the user. The RaiseSignal method is
  783.          implemented as follows:
  784.  
  785.          procedure MasterStringIOOBJ.RaiseSignal(var TheSig:tSignal);
  786.          {}
  787.          begin
  788.             with TheSig do
  789.             begin
  790.                ID := SignalNewDirectory;
  791.                MsgType := length(vInputStr);
  792.                MsgPtr := @vInputStr;
  793.             end;
  794.             vLastInput := vInputStr;
  795.          end; {MasterStringIOOBJ.RaiseSignal}
  796.  
  797.          The signal ID is set to SignalNewDirectory --  a constant assigned the
  798.          value of 1. The MsgType field is set to indicate the length of the
  799.          string, and the variant record MsgPtr is updated to point to the user
  800.          input string. This signal, therefore, provides sufficient data for the
  801.          dependent fields to ascertain the new directory.
  802.  
  803.          In this example, no dynamic data is created for the signal, and so the
  804.          ShutdownSignal method doesn't need to do anything.
  805.  
  806.  
  807.          Now let's turn our attention to the object which needs to respond to
  808.          the signal raised by MasterStringIOOBJ. In this example, we will name
  809.          the new object SlaveStringIOOBJ, and it will inherit all the properties
  810.          of StringIOOBJ. The only method (in addition to Init and Done) which
  811.          needs to be updated is HandleSignal. This method needs to check the
  812.          value of the Signal and update the value of the field with the new
  813.          directory string. The signal field MsgType stores the length of the new
  814.          string, and the field MsgPtr points to the new string.
  815.  
  816.          The HandleSignal method is implemented as follows:
  817.  
  818.          procedure SlaveStringIOOBJ.HandleSignal(var BaseSig:tSignal; var NewS-
  819.          ig:tSignal);
  820.          {}
  821.          var temp:string;
  822.          begin
  823.             with BaseSig do
  824.             begin
  825.                if (ID = SignalNewDirectory) then
  826.                begin
  827.                   move(MsgPtr^,Temp,succ(MsgType));
  828.                   if Temp <> vInputStr then
  829.                   begin
  830.                      vInputStr := Temp;
  831.  
  832.  
  833.  
  834.  
  835. 20-16                                                      Extending the Toolkit
  836.  
  837. --------------------------------------------------------------------------------
  838.  
  839.                      Display(Norm);
  840.                   end;
  841.                end;
  842.             end;
  843.          end; {SlaveStringIOOBJ.HandleSignal}
  844.  
  845.  
  846.  
  847.  
  848.          To recap, two new field objects have been created. A MasterStringIOOBJ
  849.          field raises a signal when its value is changed, and SlaveStringIOOBJ
  850.          fields change their value accordingly. The on-disk demo file EXT-
  851.          DEM8.PAS includes the full solution to the problem. Figure 20.2 illus-
  852.          trates the output generated by this program.
  853.  
  854.  
  855.  
  856. Figure 20.2                                                             [SCREEN]
  857. Raising
  858. Signals
  859.  
  860.  
  861.  
  862.          Review the source code of the DirWinOBJ object in totDIR for another
  863.          example of how fields can communicate with signals.
  864.