Squeak SmalltalkJoker Squeak Smalltalk : Algorithm : prevnext Hierarchical State Machine Harel Semantics

In my Connectors 2 package (available on SqueakMap) there is code for a 
hierarchical state machine (supporting the full Harel semantics), and a Morph 
class that uses these state machines for event handling.

You can add to or modify existing state machine definitions in two directions: 
you can define sub-states to provide specialization, and/or you can inherit 
from existing event handler classes and override their event handling 
methods.

The stock QHsmMorph has the following state hierarchy. Note that it does not 
use the MouseClickState state machine, handling click and double-click 
detection in its own handlers.

Top
 Global
  WaitingForDoubleClick
   FirstClickUp (MouseDown)
   WaitingForDoubleClickWithMouseDown
    SecondClickDown (MouseUp)
    FirstClickDown (MouseUp)
  Idle
  Dragging
 Initial

Events that occur in a sub- (nested) state like FirstClickUp can be handled in 
the state handlers for FirstClickUp, its parent WaitingForDoubleClick, or its 
parent Global. There is one method per state. Optionally, you can specialize 
by providing a method for a given kind of event in a given state. There are 
three examples of this here (they are shown by parentheses).

This is based on the idea (strange as it seems) of generating method selectors 
based on the current state and maybe also the type of the event.

So in this case, the corresponding method selectors are:

Top => stateTop
 Global => stateGlobal
  WaitingForDoubleClick => stateWaitingForDoubleClick
   FirstClickUp (MouseDown)  => handleMouseDownInFirstClickUp
   WaitingForDoubleClickWithMouseDown => 
stateWaitingForDoubleClickWithMouseDown
    SecondClickDown (MouseUp) => handleMouseUpInSecondClickDown
    FirstClickDown (MouseUp)  => handleMouseUpInFirstClickDown
  Idle => stateIdle
  Dragging => stateDragging
 Initial  => stateInitial

Each event has a symbolic type and an optional event (perhaps a MorphicEvent) 
associated with it.

When an event is handled, first we make a specialized selector using the 
symbolic type of the event pasted together with the current state name:

 'handle' + <event type> + 'In' + <current state name>

If there is no interned symbol matching that, or if the event handler does not 
understand that selector, then we try the more general state handler:

 'state' + <current state name>

If neither selector matches, there is a catch-all state handler for unhandled 
states.

As goofy as this sounds, it results in a clean separation of state handling 
code.

Of course, I sometimes end up with a case switch inside the state handler 
methods to handle the individual event responses. Here's a snippet from the 
above QHsmMorphEventHandler (event is an instance variable that holds the 
MorphicEvent being handled):

stateDragging: evt 
 ^evt caseOf: { 
    [#entry] ->  [self startDrag: event. nil].
    [#exit] ->  [self stopDrag: event. nil].
    [#mouseUp] -> [ self returnToIdleHistory ]}
  otherwise: [self state: #Global]

It's possible to hold states in variables, like here where we return to 
history:

returnToIdleHistory
 "Idle history has been saved; transition back to it (and clear it)"
 | history |
 history _ idleHistory ifNil: [ #Idle].
 idleHistory _ nil.
 mySource ~= myState
  ifTrue: [self log: #newState items: {myState stateName. mySource stateName. 
history. 'idle history' }]
  ifFalse: [self log: #newState items: {myState stateName. history. 'idle 
history'}].
 ^self newDynamicState: history.


The return value from the state handlers is either nil if there is no change 
in state, or a QState object representing the new state when a transition is 
desired.

Hope this helps,
-- Ned Konz