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
--|
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:
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:
Post a Comment