2.4 Adding Functionality to a Watch Model
As the final exercise in our Watch example we will build new
functionality into an existing sub-application by modifying its state machine
definitions. In the Graph Browser, open the ‘Stopwatch’
WatchApplication diagram. The diagram will appear as shown in
Figure 2-8.
Figure 2-8. The ‘Stopwatch’ WatchApplication diagram.
As
the name says, ‘Stopwatch’ is a sub-application that enables the
user to time the length of various events. When this application is activated,
it immediately enters the ‘Stopped’ state showing the zeroed time
counter on the display. From here there are three ways to proceed. Pressing
‘Mode’ will deactivate the application and pass the control back to
the top-level state machine. Pressing ‘Down’ will reset the counter.
Pressing ‘Up’ will start the counter by setting the start time and
entering the ‘Running’ state. Pressing ‘Up’ again while
in ‘Running’ state will stop the counter by calculating the stop
time and returning back to the ‘Stopped’ state. It is also possible
to terminate the application by pressing ‘Mode’ while in the
‘Running’ state.
However, there is still something missing from our
‘Stopwatch’ sub-application: it does not have a lap-time function.
Basically, to add such a function we need to specify that when
‘Down’ is pressed while the ‘Running’ state is active,
the lap-time is calculated (as it is not available as a pre-defined entity) and
a new ‘LapTime’ state will be activated, showing the lap-time on the
display. When ‘Down’ is pressed again, the control is passed back to
the ‘Running’ state.
Before proceeding with any new functionality, let us
explore the basic mechanism of how the time shown on the display is controlled.
Each application state refers to a display function that specifies how to
calculate the time shown while that state is active. Display functions are
presented with DisplayFn objects (two green boxes at the top of
Figure 2-8) and each of them can be shared
by many states. The green text at the bottom right corner of the state symbol
indicates which display function it refers to. There are two display functions
in our Stopwatch application, one called ‘Running’ and one without a
name. Leaving the name blank is the way to define a default display function
– all the states without an explicitly named display function will use
this nameless display function.
The display function definition also sets the key time
unit that will be shown as the middle one on the display. This allows us to
specify that a three-zone watch can display hours, minutes and seconds in the
normal Time application, but minutes, seconds and hundredths in the Stopwatch.
The actual time unit arithmetic is based on variables and
variable references that are services built into our framework. For example, the
display function ‘Running’ returns the current counter time by
subtracting the value of the ‘startTime’ variable from the value of
the ‘sysTime’ variable reference, which holds the current system
time.
As for the first task of creating the lap-time
functionality, create a new State object and enter ‘LapTime’ as its
name. As the default display function suits our purposes here and as we do not
need a blinking display, you can leave the other property fields blank. Continue
by defining a Transition relationship from the ‘Running’ state to
‘LapTime’ and then another from ‘LapTime’ back to
‘Running’.
The next thing we need to do is to associate the
‘Down’ button as the triggering event for both of these transitions.
We do not need to define a new button as we can reuse an existing button
definition. Select the existing ‘Down’ button, copy it with Ctrl+C,
and paste it with Ctrl+V. The pasted button will follow the cursor: move it down
to near the ‘LapTime’ state (
Figure 2-9) and click to place it there.
Associate this button with both transitions between ‘LapTime’ and
‘Running’ states. For each transition, first select the relationship
by clicking on the small bright red dot at its corner point or mid-point, then
select
Add a New Role... from its pop-up menu and connect the new role to
the ‘Down’ Button. The diagram should now look similar to
Figure 2-9.
Figure 2-9. The ‘Stopwatch’ diagram with definitions for a new state.
To
complete the implementation of the new functionality, we need to define the
actions that are required for calculating the lap-time during the transition
from the ‘Running’ state to the ‘LapTime’ state. First
create a new Action object, and add a new role to it from the Transition
relationship going from ‘Running’ to ‘LapTime’. Then
reuse the sysTime VariableRef object and startTime and stopTime Variable objects
from this same graph as shown in
Figure
2-10.
Figure 2-10. The extended version of ‘Stopwatch’ sub-application.
To
define the calculation relationship between Action, VariableRef and Variable
objects, hold shift down and select first the Action object, then
‘sysTime’, then ‘startTime’ and finally
‘stopTime’ and then select Connect from the popup menu. From
the list of possible relationship combinations, choose ‘Set (ActionBody
Action) (Get sysTime) (Minus startTime) (Set stopTime)’ and accept the
following dialog with no changes. The definition of the lap-time functionality
has been now completed. The lap-time will be shown correctly as our calculation
stores the lap-time value in the ‘stopTime’ variable that is
attached to the default display function used by the ‘LapTime’
state. You can now try it out by generating the code and running the test
environment.
| Perceptive
readers will notice that we should actually call this a SplitTime function: on
2nd and subsequent laps, it shows the total time rather than the time
for just the previous lap. Feel free to rename the state, and why not add a true
LapTime state? There are several ways to do it, but perhaps the most natural is
to copy the stopTime into a new prevSplit variable when returning to Running,
and have a new lapTime DisplayFn that subtracts prevSplit from stopTime. You can
zero prevSplit when you zero stopTime: see the next section for a way to make
that
easier. |