7.5 State

In DrGeo, when the user clicks on a button to operate on the sketch, it is selecting a tool: to move items, to edit the styles, to delete items, to create new ones, etc.

A tool is often associated with a builder, and it can handle different states through dedicated methods. Possible states are: handleChoice:, handleMouseAt:, handlePress:, handlePressShiftKey:, handleRelease, handleReleaseShiftKey, handleShiftKey:, handleShiftKeyMouseAt:, handleStillPress:

The DrGDrawable class, a Morph used as a canvas for the sketch, is responsible for the low-level duty of translating user actions with the pointer and keyboard into message sending to one of the methods enumerated above.

The messages are sent to the current tool. For example, when the pointer is hovering over the sketch:

DrGDrawable>>mouseHover: evt localPosition: localEventPosition
"handle mouse move with button up"
localEventPosition = prevMousePos ifTrue: [^ self]. "nothing to process"
evt shiftPressed 
   ifTrue: [self tool handleShiftKeyMouseAt: localEventPosition]
   ifFalse: [self tool handleMouseAt: localEventPosition].
prevMousePos := localEventPosition

Or when the user clicks somewhere with button 1:

DrGDrawable>>mouseButton1Down: evt localPosition: localPosition
evt shiftPressed 
   ifTrue: [self tool handlePressShiftKey: localPosition ]
   ifFalse: [self tool handlePress: localPosition].
self showUnderMouseMorph

Or when button 2 is released:

DrGDrawable>>mouseButton1Up: evt localPosition: localPosition
evt shiftPressed 
   ifTrue: [self tool handleReleaseShiftKey: localPosition]
   ifFalse: [self tool handleRelease: localPosition ].
(self localBounds containsPoint: localPosition) 
   ifFalse: [Cursor normalCursorWithMask activateCursor]

The state design pattern works with two hierarchies of classes: the contexts and the states. In DrGeo, a context is the tool currently in use, and DrGTool is the root of this hierarchy. A tool can be in various internal states; therefore, the tool dispatches to its internal state the responsibility to handle the user actions:

DrGTool>>handleMouseAt: aPoint
^ self state handleMouseAt: aPoint

Or

DrGTool>>handlePressShiftKey: aPoint
^ self state handlePressShiftKey: aPoint

The DrGBuildTool is the tool to construct new items; it comes with a builder, as described in Builder:

DrGTool subclass: #DrGBuildTool
   instanceVariableNames: 'selectedMorphs builder'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'DrGeo-Presenter-Tool'

When a tool is initialized, it is set to a default neutral state:

DrGBuildTool>>initialize
super initialize.
self switchState:  DrGBuildToolState.
selectedMorphs := OrderedCollection new
DrGTool>>switchState: aStateClass
self state: (aStateClass new context: self)

The build tool lets the user define a new mathematics item by selecting existing items in a sketch. The relevant message is handlePress::

DrGBuildToolState>>handlePress: aPoint
"Return true if we process something (including additional user choice)"
| morphs |
self drawable hideTip.
self context last: aPoint.
morphs := self context relevantMorphsNear: aPoint.
morphs size = 1 ifTrue: [
   self handleChoice: morphs.
   ^ true].
(morphs size = 0 and: [self context isWanted: #() ]) ifTrue: [
   self handleChoice: morphs.
   ^ true].
"More than one math item under mouse, user must choose one item"
morphs size >= 2 ifTrue: [
   "Display a pop-up menu to select one item"
   self context chooseMorph: morphs.
   ^ true].
^ false

The DrGBuildTool has only one state. The DrGSelectTool is much more interesting as it comes with 9 different states.

With this tool, the user interacts with the sketch by dragging items, reassigning points, or even cloning items.

initialize
self reset.
builder := DrGCloneBuilder new

The tool is initialized in a neutral state:

reset
super reset.
start := nil.
self switchState: DrGSelectToolStateNeutral.
mathItems := nil.
builder ifNotNil: [builder reset]

It is therefore enlightening to look at the handlers of this state. For example, when hovering a sketch (#handleMouseAt:), the pointer shape is adjusted to a pointing hand to inform the user that something can be grabbed:

DrGSelectToolStateNeutral>>handleMouseAt: aPoint
| processSomething |
(processSomething := super handleMouseAt: aPoint)
   ifTrue: [Cursor webLinkCursor activateCursor] 
   ifFalse: [Cursor normalCursorWithMask activateCursor].
^ processSomething 

More interestingly, when the mouse button is pressed (#handlePress:)on a relevant item, the tool is switched to a grabbed state with class DrGSelectToolStateGrabbed:

DrGSelectToolStateNeutral>>handlePress: aPoint
| morphs griddedPoint|
self drawable hideTip.
griddedPoint := self context gridPoint: aPoint.
morphs := self context relevantMorphsNear: aPoint.
morphs size = 1 
   ifTrue: [
      self context last: griddedPoint.
      self context morph: morphs first.
      self context updateDirtyItemsList.
      self switchState: DrGSelectToolStateGrabbed.
      ^ true ].
"More than one math item under mouse"
morphs size > 1 
   ifTrue: [
      self context last: griddedPoint.
      self context chooseMorph: morphs.
      ^ true ].
"The user clicked in the background, clear the selection"
self context reset.
^ false

It offers two dedicated handlers. In case the mouse button is released (#handleRelease:) while in the grabbed state, the tool switches back to the neutral state:

DrGSelectToolStateGrabbed>>handleRelease: aPoint
self switchState: DrGSelectToolStateNeutral.
self context reset.
"After move event rehash the the free positionable item"
self context factory rehash

If instead the mouse is moving (#handleMouseAt:), the tool switches to a dragged state with class DrGSelectToolStateDragged:

DrGSelectToolStateGrabbed>>handleMouseAt: aPoint
"The user is moving, switch to dragging state"
self context
   start: aPoint;
   last: aPoint.
self context morph isBitmap 
   ifTrue: [self switchState: DrGSelectToolStateDraggedBitmap] 
   ifFalse: [self drawable dottedLinesToParentsOf: self mathItem.
      self switchState: DrGSelectToolStateDragged].
^ true

Understanding how the state of a given tool – or context in the State design pattern terminology – changes is complex to follow. Knowing how the pattern works is fundamental to understanding what is going on. This pattern proved to be useful to handle the complexity of a collection of GUI tools, each with its own way of interacting with the UI environment.

Sequence diagrams help to have a global understanding of the flow of states in a given tool.

ch07-statesSequence

Figure 7.5: States sequence of the Select tool