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. Possibles state are: handleChoice:, handleMouseAt:, handlePress:, handlePressShiftKey:, handleRelease, handleReleaseShiftKey, handleShiftKey:, handleShiftKeyMouseAt:, handleStillPress:
The DrGDrawable class, a Morph used as a canvas for the sketch, is responsible of the low level duty of translating user action with pointer and keyboard to message sending to one of the methods enumerated above.
The messages are sent to the current tool. For example, when the pointer is hovering 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 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, DrGTool is the root of this hierarchy. A tool can be in various internal state, therefore the tool dispatch 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 item, its 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 point 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 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 grabbed state, the tool switches back to neutral state:
DrGSelectToolStateGrabbed>>handleRelease: aPoint
self switchState: DrGSelectToolStateNeutral. self context reset. "After move event rehash the the free positionnable 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 understand what is going on. This pattern proved to be useful to handle the complexity of a collection of GUI tools, each one with its own way to interact with the UI environment.
Sequence diagram helps to have a global understanding of the flow of states in a given tool.
Figure 7.5: States sequence of the Select tool