Thursday, August 2, 2012

Using A661 in a Windows PC Environment – part 2



This post will illustrate how easy it is to expand the User/Worker application to treat a new Definition File widget – while no change is necessary to the CDS/Display application – using my design.  This post illustrates the actual steps that were necessary.

To determine what needs to be done to add a button and associated widget action I added a new button of Extra to the Definition File.

Step 1) Add the xml for the button to the Definition File.

After adding the xml for the new button the DF is as follows.
<A661_LAYER
      DESCRIPTION="GUI1 Layer"
      LAYER_IDENT="3002"
      NET_PORT="DisplayAppDuplexPipeLayer"
      POS_X="0"
      POS_Y="0"
      SIZE_X="800"
      SIZE_Y="800"
      UNIT_NAME="GUI1"
      FORM_IDENT="3000"
      ENABLE="A661_FALSE"
      VISIBLE="A661_TRUE"
      SCALE_WIDTH="6"
      SCALE_HEIGHT="13"
      FORM_BORDER="FixedDialog">
      <WIDGET
            DESCRIPTION="GUI1 Layer"
            WIDGET_TYPE="A661_BASIC_CONTAINER"
            WIDGET_IDENT="3101"
            PARENT_IDENT="3000"
            ENABLE="A661_TRUE"
            VISIBLE="A661_TRUE"
            POS_X="0"
            POS_Y="0">
         . . .
            <WIDGET
                  DESCRIPTION="Basic Container for Map"
                  WIDGET_TYPE="A661_BASIC_CONTAINER"
                  WIDGET_IDENT="3099"
                  PARENT_IDENT="3101"
                  ENABLE="A661_TRUE"
                  VISIBLE="A661_TRUE"
                  POS_X="000"
                  POS_Y="50">
                  <WIDGET
              . . .
                        <WIDGET
                              DESCRIPTION="Lat Lon Map Source"
                              WIDGET_TYPE="A661_MAPHORZ_SOURCE"
                              WIDGET_IDENT="3006"
                              PARENT_IDENT="3001"
                              ENABLE="A661_TRUE"
                              VISIBLE="A661_TRUE"
                              MAPHORZ_DATA_FORMAT="A661_MDF_LAT_LONG"
                              EVENT_FLAG="A661_TRUE"
                              UNUSEDPAD16="0">
                              <WIDGET
                                    DESCRIPTION="Map Item List"
                                    WIDGET_TYPE="A661_MAPHORZ_ITEMLIST"
                                    WIDGET_IDENT="3003"
                                    PARENT_IDENT="3006"
                                    ENABLE="A661_TRUE"
                                    VISIBLE="A661_TRUE"
                                    MAX_NUMBER_OF_ITEM="600"
                                    UNUSEDPAD8="0"/>
                        </WIDGET>
                  </WIDGET>
                  <WIDGET
                        DESCRIPTION="Extra Button"
                        WIDGET_TYPE="A661_PICTURE_PUSH_BUTTON"
                        WIDGET_IDENT="3008"
                        PARENT_IDENT="3099"
                        ENABLE="A661_TRUE"
                        VISIBLE="A661_TRUE"
                        POS_X="20"
                        POS_Y="80"
                        SIZE_X="150"
                        SIZE_Y="30"
                        STYLE_SET="11"
                        FOCUS_INDEX="0"
                        MAX_STRING_LENGTH="30"
                        INNER_STATE_CHECK="A661_UNSELECTED"
                        LABEL_POSITION="A661_LEFT"
                        AUTO_FOCUS_MOTION="A661_TRUE"
                        UNUSEDPAD8="0"
                        UNUSEDPAD16="0"
                        STRING="Extra">
                  </WIDGET>
                  <WIDGET
                        DESCRIPTION="Data Panel display toggle"
                        WIDGET_TYPE="A661_PICTURE_PUSH_BUTTON"
                        WIDGET_IDENT="3007"
                        PARENT_IDENT="3099"
                        ENABLE="A661_TRUE"
                        VISIBLE="A661_TRUE"
                        POS_X="20"
                        POS_Y="40"
                        SIZE_X="150"
                        SIZE_Y="30"
                        STYLE_SET="11"
                        FOCUS_INDEX="1"
                        MAX_STRING_LENGTH="14"
                        INNER_STATE_CHECK="A661_UNSELECTED"
                        LABEL_POSITION="A661_LEFT"
                        AUTO_FOCUS_MOTION="A661_TRUE"
                        UNUSEDPAD8="0"
                        UNUSEDPAD16="0"
                        STRING="Data Panel">
                  </WIDGET>
                . . .
                  </WIDGET>
            </WIDGET>
            <WIDGET
                  DESCRIPTION="Time Label"
                  WIDGET_TYPE="A661_LABEL"
                  WIDGET_IDENT="3022"
                  PARENT_IDENT="3101"
                  ENABLE="A661_TRUE"
                  VISIBLE="A661_TRUE"
                  POS_X="200"
                  POS_Y="15"
                  SIZE_X="32"
                  SIZE_Y="30"
                  STYLE_SET="1"
                  FOCUS_INDEX="0"
                  MAX_STRING_LENGTH="6"
                  AUTO_FOCUS_MOTION="A661_FALSE"
                  ALIGNMENT="A661_LEFT"
                  STRING="Time">
            </WIDGET>
            <WIDGET
                  DESCRIPTION="Time Value"
                  WIDGET_TYPE="A661_EDIT_BOX_TEXT"
                  WIDGET_IDENT="3023"
                  PARENT_IDENT="3101"
                  ENABLE="A661_TRUE"
                  VISIBLE="A661_TRUE"
                  POS_X="232"
                  POS_Y="13"
                  SIZE_X="200"
                  SIZE_Y="30"
                  STYLE_SET="1"
                  FOCUS_INDEX="0"
                  MAX_STRING_LENGTH="20"
                  AUTO_FOCUS_MOTION="A661_FALSE"
                  ALIGNMENT="A661_LEFT"
                  STRING="        ">
            </WIDGET>
      </WIDGET>
</A661_LAYER>

Note that this Definition File xml has container widgets and those for a map where the map controls/widgets have not yet been implemented in the Display program and hence are ignored.  The added xml is
                  <WIDGET
                        DESCRIPTION="Extra Button"
                        WIDGET_TYPE="A661_PICTURE_PUSH_BUTTON"
                        WIDGET_IDENT="3008"
                        PARENT_IDENT="3099"
                        ENABLE="A661_TRUE"
                        VISIBLE="A661_TRUE"
                        POS_X="20"
                        POS_Y="80"
                        SIZE_X="150"
                        SIZE_Y="30"
                        STYLE_SET="11"
                        FOCUS_INDEX="0"
                        MAX_STRING_LENGTH="30"
                        INNER_STATE_CHECK="A661_UNSELECTED"
                        LABEL_POSITION="A661_LEFT"
                        AUTO_FOCUS_MOTION="A661_TRUE"
                        UNUSEDPAD8="0"
                        UNUSEDPAD16="0"
                        STRING="Extra">
                  </WIDGET>
which is mostly a copy of the xml for other button widgets making sure that the Widget Ident is unique and that the parent is the desired container and located within the other direct children of the parent widget.

Step 2) Changes to the Display/CDS Application

There are no changes to Display program.

That is, as long as the widget type has already had its code implemented in the Display program, no changes to it are necessary to add another instance of the same type of widget.  Assuming, of course, that this doesn't exceed a maximum for the particular type of widget.

Step 3) Changes to the Widget package of the User/Worker application

3a) Since the newly added widget is to be treated by the worker application, a widget name must be added to the enumerated widget name type in the Ada spec of the Widget package so it can be referenced in place of the numeric widget identifier. 

This isn't absolutely necessary; it is a convenience to allow the widget to be referenced by name throughout the application rather than by number.

Therefore, Extra was added to this type resulting in
  type Widget_Name_Type
  --| Widget names associated with widget identifiers
  is ( Unknown,     -- to be used when the CDS->UA widget identifier has no match
       GUI1,             -- layer 1 management
       GUI1_Layer,       -- layer 1 container
       Travel_Rte_Name,
       Travel_Rte_Activate,
       Travel_Rte_Go,
       Map_Toggle,       -- to toggle visibility of map container
       Map,              -- map container
       Map_Options,
       Map_Range,
       Data_Panel_Toggle,
       Data_Panel,
       Extra,
       Time,
       Data_Cancel,
       No_Action ); -- when the widget identifier is UA->CDS only and not named
  for Widget_Name_Type'size use 16; -- bits
  subtype Actual_Widget_Name_Type
  is Widget_Name_Type
  range Widget_Name_Type'succ(Unknown)..Widget_Name_Type'pred(No_Action);


This table allows the widget identifier to be located in the Widget_Id_Lookup table when the widget name is supplied for its lookup.  This also allows the widget identifier to be supplied when building an A661 message to be transmitted to the Display application.

After the addition, the table becomes
  Widget_Name_Lookup
  --| Indexes into sorted widget identifier table by widget name.
  --| Notes:
  --|   This table is sized by the range of the enumerated widget name type
  --|   with the indexes added at power-up.
  : Widget_Name_Lookup_Array_Type
  := ( Unknown            => ( Widget_Id => 0,
                               Index     => 0 ),
       GUI1               => ( Widget_Id => 3000,
                               Index     => 0 ),
       GUI1_Layer         => ( Widget_Id => 3101,
                               Index     => 0 ),
       Travel_Rte_Name    => ( Widget_Id => 3017,
                               Index     => 0 ),
       Travel_Rte_Activate=> ( Widget_Id => 3018,
                               Index     => 0 ),
       Travel_Rte_Go      => ( Widget_Id => 3019,
                               Index     => 0 ),
       Map                => ( Widget_Id => 3099,
                               Index     => 0 ),
       Map_Toggle         => ( Widget_Id => 3110,
                               Index     => 0 ),
       Map_Options        => ( Widget_Id => 3021,
                               Index     => 0 ),
       Map_Range          => ( Widget_Id => 3020,
                               Index     => 0 ),
       Data_Panel_Toggle  => ( Widget_Id => 3007,
                               Index     => 0 ),
       Data_Panel         => ( Widget_Id => 3405,
                               Index     => 0 ),
       Extra              => ( Widget_Id => 3008,
                               Index     => 0 ),
       Time               => ( Widget_Id => 3023,
                               Index     => 0 ),
       Data_Cancel        => ( Widget_Id => 3416,
                               Index     => 0 ),
       No_Action          => ( Widget_Id => 0,
                               Index     => 0 )
     );

The Index values will be filled in after the Definition File is read at startup.  The sorted table is one sorted by widget id for the ability to do a binary search when finding the data using the widget identifier available from a received widget event message.

3c) Since the widget event message for the new widget is to be acted upon by the worker application, a new entry is also added to the User_Widget_Data table. 

Only those widgets that are (or may become) of concern to the application are in this table.  During the load of the DF, widgets that are not in this table will be ignored. 

To allow widgets to be added as they become known to be needed, this table is a list.  That is, it has a count of the number of entries that are of interest and then a list of those entries.  Additional entries can follow to avoid needing to change the size of the array each time a widget is added.  Therefore, when a new entry is added it can be done by modifying the first spare entry and changing the Count of usable entries. 

The entries have the widget identifier and name pair as well as additional data that may be of help to the application that isn't part of the A661 specification.  For purposes of illustration, a Redisplay field and a widget Category field are shown.

When an entry is looked up by identifier, it is located in the sorted table that has its index in the list.

Due to the additional widget to be treated, the User_Widget_Data list Count of widgets of interest was , increased by 1 to 31 and the entry at position 31 of the list was modifyied to provide the widget id and name and that it was to be updated upon an action.

The new table thus became
  User_Widget_Data
  --| Pre-built data of each widget
  --| Notes:
  --|   o Records can be added to this table as they become known by
  --|     incrementing the Count and adding the new record at the new Count
  --|     position.  The widget ids of the entries along with their location
  --|     in this table will be added to the Widget_Id_Lookup at power-up.
  --|   o Additional space for records can easily be added by increasing the
  --|     allowed range of the Widget_Range_Type by increasing the range of
  --|     the Widget_Count_Type.
  : constant User_Widget_Data_List_Type
  := ( Count => 31,   --     Id   Name          Redisplay     Category
       List  => (  1 => ( ( 3000, GUI1       ), Upon_Action, Container ),
                           -- layer form only for the Windows version
                   2 => ( ( 3101, GUI1_Layer ), Static,      Container ),
                           -- layer container
                   3 => ( ( 3110, Map_Toggle ), Upon_Action, Dont_Care ),
                   4 => ( ( 3106, No_Action ), Static,      Dont_Care ),
                   5 => ( ( 3099, No_Action ), Redisplay,   Container ),
                           -- map container
                  30 => ( ( 3023, Time   ), Dynamic,     Dont_Care ), -- Time
                  31 => ( ( 3008, Extra ),  Upon_Action, Dont_Care ), -- Extra
                  32 => ( ( 0, No_Action ), Static, None ), -- next available
                            "     "           "      "
                       . . .

3d) To treat the new widget event, that results from clicking the new Extra button on the display, a new Widget Action component needs to be added to the worker application.  This component must be "installed" with the framework.  (See the diagram of the previous post that illustrates the framework and the Widget and Widget Action components.)

To do so, the Ada "with" statement for the component was added to the Widget package body and a call to its Install procedure was made from the Install procedure of the Widget package (which is really an Initialization procedure since the Widget package is not a component – that is, it doesn't have its own thread).  In that way, these worker application modifications are limited to the Widget package.
The added component was named Extra_Com since Ada doesn't allow the "with"ed package name to be the same as the enumerated literal name.

Step 4) Addition of the Widget Action component

The Extra_Com component Ada package spec and body were created using two other such packages as examples; one a widget action component and one that output text to the A661 message to be transmitted.

As mentioned in the previous blog post, the component's callback is run by the framework when the widget event request topic is published.  This acts the same as Windows invoking the event handler supplied for a widget.  In the case of the worker application, the component supplies the callback when it registers with the framework to treat the widget event topic.  This is similar to the display app supplying the handler callback when it "registers" the button with Windows.

To this, the code copied from another widget action component is used as is except for one minor change.  That is, in its Install procedure the code
   --| Logic_Step:
    --|   Register to consume the general widget event topic that causes this
    --|   component to be run.

    declare

      Delivery_Id_List
      --| Treat all A661 messages to be treated by this widget action component
      : mC.Itf_Types.Delivery_Id_Array_Type
      := ( ( Integer(Widget.Ident(Widget.Extra).Ident), 0 ),
           ( 0, 0 ), ( 0, 0 ), ( 0, 0 ), ( 0, 0 ) );

      Delivery_Id
      --| Deliver topic when identifier is that of widget identifiers
      : mC.Itf_Types.Delivery_List_Type
      := ( Count => 1,
           List  => Delivery_Id_List );

    begin

      A661.Topics.Widget_Event.Request.Data.Register
      ( Participant => Component_Main_Key,
        Detection   => mC.Itf_Types.Event_Driven,
        Role        => mC.Itf_Types.Consumer,
        Delivery_Id => Delivery_Id,
        Callback    => Treat_A661_Widget_Event'access,
        Access_Key  => Widget_Event_Topic_Access_Key,
        Status      => Data_Status );

    end;

    --| Logic_Step:
    --|   Register to produce the widget event done response topic that causes
    --|   the layer management component that sent the request to be run.

    A661.Topics.Widget_Event.Response.Data.Register
    ( Participant => Component_Main_Key,
      Detection   => mC.Itf_Types.Event_Driven,
      Role        => mC.Itf_Types.Producer,
      Access_Key  => Widget_Event_Done_Access_Key,
      Status      => Data_Status );

was used where only "Integer(Widget.Ident(Widget.Extra).Ident), 0 )," of the Delivery_Id_List was changed to use the widget name Extra to obtain the widget identifier to be used as the delivery identifier.  Therefore, a common single widget procedure could be added to register the Widget Event Request by supplying the widget name and getting the access key and status returned.

The other changes have to do with what the action is to do.  In this case it was decided to display different text on the button instead of hiding certain buttons and making others visible.  As such the contents of the Treat_A661_Widget_Event callback procedure were modified. 

Of course, the amount of effort depends upon what the component needs to accomplish which would be more in a real application.  (The amount of effort in any particular component depends upon whether other components can be used to perform portions of the work.  If so, topics can be published to the framework to run the component and get the results back via another callback.)

For the simple job of changing the displayed text of the button, the following callback is sufficient.

  procedure Treat_A661_Widget_Event
  ( Topic : in mC.Topic_Name_Type
  ) is
  -- ++
  --| Notes:
  --|   1. Widget event not expected to have a size different than 8.  Not
  --|      checked for nor treated.
  --|   2. This widget action procedure is for illustration purposes.  It
  --|      only displays new text for the button's label.
  --|   3. If the widget event cannot be treated, the Done topic is published
  --|      to indicate that the Layer Management component can treat the next
  --|      received command.
  -- --

    Treated
    --| True for event treated
    : Boolean := False;

    Widget_Info
    --| Widget ident and layer
    : Widget.Widget_Layer_Pair_Type;

    package Reader
    is new A661.Topics.Widget_Event.Request.Data.Reader
           ( Access_Key => Widget_Event_Topic_Access_Key,
             Fresh_Data => True ); -- no matter

    package Writer
    is new  A661.Topics.Widget_Event.Response.Data.Writer
            ( Access_Key        => Widget_Event_Done_Access_Key,
              Initialize_Buffer => False );

    use type Machine.Address_Int;

  begin -- Treat_A661_Widget_Event

    Widget_Info := Widget.Ident( Name => Widget.Extra );

    if Reader.Valid then

      --| Logic_Step:
      --|   Act on notification.

      declare

        Widget_Name
        --| Associated widget names
        : Widget.Widget_Name_Type;

        Widget_Event
        --| Formatted command structure overlaid upon the received message
        : A661.Types.A661_Widget_Event_Structure_Type;
        for Widget_Event'address use Reader.Ptr.Data.Request_Address;

      begin

        Widget_Name := Widget.Action( Ident => Widget_Event.Widget_Ident );

        –-|   This check is not really needed since there is only the one
        –-|   reason that this callback can be entered.
        case Widget_Name is

          --| Logic_Step:
          --|   Act on the event.

          when Widget.Extra =>
            declare

              LocBlk
              --| Location of transmit buffer block
              : Machine.Address_Int;

              LocCmd
              --| Offset into transmit buffer to a command block
              : Machine.Address_Int;

              LocSP
              --| Offset into transmit buffer to Set Parameter structure
              : Machine.Address_Int;

              OffsetCmd
              --| Byte offset into transmit buffer for a command block
              : Integer := 0;

              OffsetSP
              --| Byte offset into transmit buffer for a Set Parameter structure
              : Integer := 0;

              Text_Digits
              --| Text value as digits
              : String(1..4);

              Write_Buffer
              --| Generic write buffer located at location provided by input
              --| message
              : A661.Types.Generic_Message_Type;
              for Write_Buffer'address use Reader.Ptr.Data.Transmit_Address;

              Begin_Block
              : A661.Types.Block_Structure_RT_Begin_Type;
              for Begin_Block'Address use Reader.Ptr.Data.Transmit_Address;

            begin

              Begin_Block := ( Structure_Type => A661.Types.Begin_Block,
                               Layer_Ident    => Reader.Ptr.Data.Layer_Ident,
                               Context_Number => 0, -- set in GUI1 before it
                                                    --  transmits the message
                               Block_Size     => 0, -- to be modified by end block
                               Command        => A661.Types.Set_Parameter );
                                                 -- will be replaced below
              LocBlk := Machine.To_Address_Int(Begin_Block'address);
              OffsetCmd := A661.Types.Begin_Block_Size; -- to end of begin block
              LocCmd := LocBlk + Machine.Address_Int(OffsetCmd);
              OffsetSP := OffsetCmd + A661.Types.A661_Widget_Event_Structure_Size;
              LocSP := LocBlk + Machine.Address_Int(OffsetSP);

              --| Logic_Step:
              --|   Create new label to display on button.

              Text_Value := Text_Value + 1;
              Text_Digits := Integer_to_Ascii( Number => Text_Value,
                                               Count  => 4 );
              for I in 1..4 loop
                Text_Displayed(I+6) := Text_Digits(I);
              end loop;

              --| Logic_Step:
              --|   Output text to widget.

              declare

                Begin_Command
                : A661.Types.A661_Cmd_Set_Parameter_Structure_Type;
                for Begin_Command'address use Machine.To_Address(LocCmd);

                Index
                --| Index into Message to copy in string
                : Integer;

                Length
                --| Length of string
                : Integer;

                Pad_Count
                --| Number of NULs required for 32-bit alignment
                : Integer;

                Set_Parameter
                : A661.Types.A661_String_Parameter_Begin_Structure_Type;
                for Set_Parameter'address use Machine.To_Address(LocSP);

                Size
                --| Size of command block
                : Integer;

                function Char_to_Byte is new Unchecked_Conversion
                                             ( Source => Character,
                                               Target => Machine.Unsigned_Byte );

              begin

                Length := 11; -- string length including trailing NUL

                Size := A661.Types.A661_Cmd_Set_Parameter_Structure_Size +
                        A661.Types.A661_String_Parameter_Begin_Structure_Size;
                Index := Size; -- to first byte after the command structure
                               --  and the beginning of String structure
                Pad_Count := Length +
                             A661.Types.A661_String_Parameter_Begin_Structure_Size;
                if (Pad_Count rem 4) > 0 then -- string parameter doesn't end
                                              --  on 32-bit boundary
                  Pad_Count := 4 - (Pad_Count rem 4); -- 1, 2, or 3
                else
                  Pad_Count := 0; -- string parameter ends on 32-bit boundary
                end if;
                Size := Size + Length + Pad_Count;

                Begin_Command := ( Command      => A661.Types.Set_Parameter,
                                   Size         => A661.Types.Command_Size_Type
                                                   (Size),
                                   Widget_Ident => Widget.Ident
                                                   ( Name => Widget.Extra ).Ident,
                                   Pad          => 0 );
                Set_Parameter := ( Ident => A661.Types.String_Param,
                                   Size  => Machine.Unsigned_Word(Length) );

                Index := OffsetCmd + Index + 1; -- to first character of string
                for I in 1..10 loop -- copy string to string parameter
                  Write_Buffer(Index) := Char_to_Byte(Text_Displayed(I));
                  Index := Index + 1;
                end loop;
                Write_Buffer(Index) := 0; -- trailing NUL
                Index := Index + 1;

                for I in 1..Pad_Count loop
                  Write_Buffer(Index) := 0; -- extra NUL
                  Index := Index + 1;
                end loop;

                OffsetCmd := Index - 1; -- offset rather than array index
                LocCmd := LocBlk + Machine.Address_Int(OffsetCmd);

              end;

              --| Logic_Step:
              --|   Do the end of block.

              declare

                End_Block
                : A661.Types.End_Block_Structure_Type;
                for End_Block'address use Machine.To_Address(LocCmd);

              begin

                End_Block.Structure_Type := A661.Types.End_Block;
                End_Block.Pad := ( others => 0 );

                OffsetCmd := OffsetCmd + A661.Types.End_Block_Structure_Size;

                --| Logic_Step:
                --|   Fill total size into the beginning of the block.

                Begin_Block.Block_Size := OffsetCmd;

              end;

              Treated := True;

            end;

          when others =>
            null; -- unexpected; Treated remains False

        end case;

      end;

    end if;

    --| Logic_Step:
    --|   Publish the message to be transmitted or Done.

    if Writer.Valid then

      Writer.Ptr.Data.Transmit := Treated;
      Writer.Publish;

    end if;

  end Treat_A661_Widget_Event;


Step 5) Announce the new widget action component to the compiler

Added the Extra_Com spec and body to the GNAT Ada GPS project.  Added the compile of the body to the bat file for compile and build as
c:\gnat\2011\bin\g++ -c -g -IC:\Source\EP\UserApp\A661UA-1
 -IC:\Source\EP\UserApp\A661UA-1\App1 -IC:\Source\EP\Util -IC:\Win32Ada\src
 -aLC:\Source\EP\Object1 C:\Source\EP\UserApp\A661UA-1\app1\Extra_Com.adb
 -o Extra_Com.o
where all lines except the first are really on the first line.


Results:

With the addition of the new button, the layer initially displays as below where everything is grayed out because the layer, and hence the main Windows form, are not active.
At startup

Then, when the connection is made with the worker application, the worker application layer management transmits the layer command to make it active and the buttons become active because the layer is.  This change isn't illustrated since the effect can also be seen in the following screen shot where the Data Panel button has been clicked to show the data panel.
After Data Panel Click
The third screen shot shows how the text of the Extra button has changed to include the count of the number of times clicked.  Each time clicked the trailing numeric value will increase by 1.

Showing Extra   1 as button text









No comments: