DCIprogram: #MoveShape
" Exported from Squeak 7179-basic.226 last modified on 3 November 2014 by: Trygve"
projection: Context
Class: MoveShape
Context subclass: #MoveShape
instanceVariableNames: 'shapeName deltaPoint drawing'
category: 'MoveShape-Context'
" System operation.
Consider a Draw progam such as the PowerPoint slide editor. A user can create and place different shapes such as ovals, polygons, lines and other shapes. A Connector is a special line that runs from one shape to another. The line will always connect the two shapes even if the shapes move. This program implements the shape:move: operation.
A NOTE ON THE IMPLEMENTATION
The code supports that a Role can be played by a Collection, in this case a collection of Connectors.
There is one Context class with one system operation:
shape: aShape move: aPoint inData: drawing
"
instanceMethods: operations
shape: shape move: delta data: aDrawing
shapeName := shape.
deltaPoint := delta.
drawing := aDrawing.
self triggerInteractionFrom: #MOVINGSHAPE with: #move.
instanceMethods: role binding
DELTA
^deltaPoint
LINE
" This role will be mapped at runtime during the enumeration of the LINECOLLECTION. "
^nil
LINECOLLECTION
| connectorSet shape |
connectorSet := Set new.
shape := drawing at: shapeName.
drawing do: [:value | ((value isKindOf: Connector) and: [value connects: shape])
ifTrue: [connectorSet add: value]].
^OrderedCollection newFrom: connectorSet
MOVINGSHAPE
^drawing at: shapeName
Interaction: MoveShape
roleMethods: MOVINGSHAPE
instanceMethods: role methods
move
MOVINGSHAPE moveMe: DELTA.
LINECOLLECTION shapeHasMoved.
roleMethods: LINECOLLECTION
instanceMethods: role methods
shapeHasMoved
LINECOLLECTION with: #LINE do:
[LINE shapeHasMoved].
roleMethods: LINE
instanceMethods: role methods
shapeHasMoved
" Make Connector go between shape centers. "
" Shape connectionPoints ignored for the time being. "
" Left to do: Make Connectors into arrows connecting shape connectorPoints. "
" Even later: Embed in a simple Draw program. (See BabyIDE) "
| shapeStart shapeEnd |
MOVINGSHAPE == LINE startShape
ifTrue:
[shapeStart := LINE endShape. shapeEnd := LINE startShape]
ifFalse:
[MOVINGSHAPE == LINE endShape
ifTrue: [shapeStart := LINE startShape. shapeEnd := LINE endShape]
ifFalse: [LINE error: 'This connector is not attached to MOVINGSHAPE']].
LINE beginPoint: shapeStart center.
LINE endPoint: shapeEnd center.
projection: Data
Class: Connector
Object subclass: #Connector
instanceVariableNames: 'startShape endShape line'
category: 'MoveShape-Data'
" The line object in the Connector is not visible from outside the Connector.
No outside code can corrupt it. "
instanceMethods: initialize-release
startShape: start endShape: end
startShape := start.
endShape := end.
(line := Line new) beginPoint: startShape center; endPoint: endShape center.
instanceMethods: attributes-read
beginPoint
^line beginPoint
endPoint
^line endPoint
startShape
^startShape
endShape
^endShape
connects: aShape
^(startShape == aShape) or: [endShape == aShape]
instanceMethods: attributes-write
beginPoint: aPoint
line beginPoint: aPoint
endPoint: aPoint
line endPoint: aPoint
pointAt: shape put: aPoint
"self halt: 'Not used'."
startShape == shape
ifTrue: [line beginPoint: aPoint rounded].
endShape == shape
ifTrue: [line endPoint: aPoint rounded].
instanceMethods: testing only
testLineStartPoint: start endPoint: end
^(line beginPoint = start) & (line endPoint = end)
Class: RectangleShape
Rectangle subclass: #RectangleShape
instanceVariableNames: ''
category: 'MoveShape-Data'
instanceMethods: attributes-read
connectionPoints
^{ self topLeft. self topCenter. self topRight. self rightCenter. self bottomRight. self bottomCenter. self bottomLeft. self leftCenter. }
instanceMethods: operations
moveMe: delta
self setOrigin: self origin + delta corner: self corner + delta.
projection: Test
Class: Test
Object subclass: #Test
instanceVariableNames: ''
category: 'MoveShape-Test'
" See UseCase comment. "
instanceMethods: operations
test
" Test new test. "
"Set up test data. "
| actA actB actC data actCmoved |
actA := RectangleShape origin: 0@6 corner: 4@4.
actB := RectangleShape origin: 0@2 corner: 4@0.
actC := RectangleShape origin: 6@4 corner: 10@2.
(data := IdentityDictionary new)
at: #actA put: actA;
at: #actB put: actB;
at: #actC put: actC;
at: #conAC put: (Connector new startShape: actA endShape: actC);
at: #conBC put: (Connector new startShape: actB endShape: actC).
" Check test Data. "
self assert: [(
(data at: #actA) = actA &
(data at: #actB) = actB &
(data at: #actC) = actC &
((data at: #conAC) testLineStartPoint: actA center endPoint: actC center) &
((data at: #conBC) testLineStartPoint: actB center endPoint: actC center)
)
].
" Move. "
MoveShape new shape: #actC move: 6@3 data: data.
" Check final Data. "
actCmoved := RectangleShape origin: 12@7 corner: 16@5.
self assert: [(
(data at: #actA) = actA &
(data at: #actB) = actB &
(data at: #actC) = actCmoved &
((data at: #conAC) testLineStartPoint: actA center endPoint: actC center) &
((data at: #conBC) testLineStartPoint: actB center endPoint: actC center)
).
].
self inform: 'MoveShape test successful.'.
classMethods: class initialization
initialize
" Test initialize "
" This magic makes ': Test move shape' appear as a choice "
" in the background menu/'open' command. "
TheWorldMenu unregisterOpenCommand: ': Test move shape'.
TheWorldMenu
registerOpenCommand:
{': Test move shape, full code'. {Test. #run.}}.
classMethods: instance creation
run
self basicNew initialize test