--- a/UIObjectView.st Tue Feb 25 14:15:56 1997 +0100
+++ b/UIObjectView.st Tue Feb 25 15:07:09 1997 +0100
@@ -1,3 +1,20 @@
+'From Smalltalk/X, Version:3.1.4 on 25-feb-1997 at 2:17:24 pm' !
+
+ObjectView subclass:#UIObjectView
+ instanceVariableNames:'inputView testMode undoHistory copiedExtent actionData
+ createClass clipChildren'
+ classVariableNames:''
+ poolDictionaries:''
+ category:'Interface-UIPainter'
+!
+
+Object subclass:#UndoHistory
+ instanceVariableNames:'history transaction enabled'
+ classVariableNames:''
+ poolDictionaries:''
+ privateIn:UIObjectView
+!
+
Object subclass:#Transaction
instanceVariableNames:'type text actions'
classVariableNames:''
@@ -5,6 +22,2064 @@
privateIn:UIObjectView::UndoHistory
!
+
+!UIObjectView class methodsFor:'defaults'!
+
+defaultGrid
+ ^ 4 @ 4
+
+!
+
+gridShown
+ ^ false
+
+!
+
+handleSize
+ "size of blob drawn for handles"
+ ^ 4
+
+!
+
+hitDelta
+ ^ 4
+
+! !
+
+!UIObjectView methodsFor:'accessing'!
+
+gridAlign
+ ^ aligning
+
+!
+
+gridAlign:aBool
+ aBool ifTrue:[self alignOn]
+ ifFalse:[self alignOff]
+
+!
+
+gridParameters
+ "used by defineGrid, and in a separate method for
+ easier redefinition in subclasses.
+ Returns the grid parameters in an array of 7 elements,
+ which control the appearance of the grid-pattern.
+ the elements are:
+
+ bigStepH number of pixels horizontally between 2 major steps
+ bigStepV number of pixels vertically between 2 major steps
+ littleStepH number of pixels horizontally between 2 minor steps
+ littleStepV number of pixels vertically between 2 minor steps
+ gridAlignH number of pixels for horizontal grid align (pointer snap)
+ gridAlignV number of pixels for vertical grid align (pointer snap)
+ docBounds true, if document boundary should be shown
+
+ if littleStepH/V are nil, only bigSteps are drawn.
+ "
+
+ ^ #(10 10 nil nil 10 10 false)
+
+
+!
+
+gridShown:aBool
+ aBool ifTrue:[self showGrid]
+ ifFalse:[self hideGrid]
+!
+
+hideGrid
+ gridShown ifTrue:[
+ self withSelectionHiddenDo:[
+ super hideGrid
+ ]
+ ]
+
+
+!
+
+showGrid
+ self withSelectionHiddenDo:[
+ super showGrid
+ ]
+
+ "Modified: 5.9.1995 / 12:47:46 / claus"
+
+
+!
+
+testMode
+ "returns testMode
+ "
+ ^ testMode
+
+
+!
+
+testMode:aBoolean
+ "change testMode
+ "
+ (aBoolean == testMode) ifFalse:[
+ testMode := aBoolean.
+
+ testMode ifTrue:[
+ self unselect.
+ inputView unrealize
+ ] ifFalse:[
+ inputView raise.
+ inputView realize
+ ]
+ ]
+
+
+! !
+
+!UIObjectView methodsFor:'blocked'!
+
+addObject:anObject
+ "add the argument, anObject to the contents - with redraw"
+
+ self halt
+
+!
+
+addObjectWithoutRedraw:anObject
+ "add the argument, anObject to the contents - with redraw"
+
+ self halt
+
+! !
+
+!UIObjectView methodsFor:'event handling'!
+
+elementChanged:aView
+ "some element has been changed - kludge to force a resizing
+ operation (for child layout recomputation) in its superView"
+
+ aView superView sizeChanged:nil.
+ self changed:#any.
+
+
+!
+
+elementChangedLayout:aView
+ "some element has been changed - kludge to force a resizing
+ operation (for child layout recomputation) in its superView"
+
+ aView superView sizeChanged:nil.
+ self changed:#layout.
+
+
+!
+
+exposeX:x y:y width:w height:h
+ "handle an expose event from device; redraw selection
+ "
+ super exposeX:x y:y width:w height:h.
+ self selectionDo:[:v | self showSelected:v]
+
+
+!
+
+keyPress:key x:x y:y
+ <resource: #keyboard ( #InspectIt #Delete #BackSpace #Cut) >
+
+ key == #InspectIt ifTrue:[
+ ^ self inspectSelection
+ ].
+
+ (key == #Cut or:[key == #Delete or:[key == #BackSpace]]) ifTrue: [
+ ^ self deleteSelection
+ ].
+
+ super keyPress:key x:x y:y
+
+
+!
+
+processEvent:anEvent
+ "catch expose events for components, and redraw its handles after
+ the redraw when this happens
+ "
+ |view|
+
+ selection notNil ifTrue:[
+ anEvent type == #damage ifTrue:[
+ view := anEvent view.
+ (selection == view
+ or:[selection isCollection
+ and:[selection includes:view]]) ifTrue:[
+ self showSelected:view
+ ]
+ ]
+ ].
+ ^ false.
+
+
+!
+
+sizeChanged:how
+ self withSelectionHiddenDo:[
+ super sizeChanged:how
+ ]
+
+
+! !
+
+!UIObjectView methodsFor:'initialization'!
+
+initialize
+ super initialize.
+
+ "funny: since I do not want the created widgets to get pointer
+ events, I put an InputView on top of them, which catches those events
+ and passes them back to me - have to take care, that this inputView
+ is always on top
+ "
+ inputView := InputView origin:0.0@0.0 extent:1.0@1.0 in:self.
+
+ inputView eventReceiver:self.
+ inputView enableButtonEvents.
+ inputView enableButtonMotionEvents.
+
+ self setDefaultActions.
+
+ undoHistory := UndoHistory new.
+ testMode := false.
+ clipChildren := true.
+
+ (self class gridShown) ifTrue:[
+ super showGrid
+ ].
+
+!
+
+realize
+ super realize.
+ self windowGroup postEventHook:self
+
+! !
+
+!UIObjectView methodsFor:'misc'!
+
+cursor:aCursor
+ inputView realized ifTrue:[
+ inputView cursor:aCursor
+ ].
+ super cursor:aCursor
+
+
+!
+
+invertOutlineOf:anObject
+ |wasClipped delta|
+
+ (wasClipped := clipChildren) ifTrue:[
+ self clippedByChildren:(clipChildren := false).
+ ].
+ delta := (anObject originRelativeTo:self) - anObject origin.
+
+ self xoring:[
+ self displayRectangle:((anObject origin + delta) extent:anObject extent).
+ ].
+
+ wasClipped ifTrue:[
+ self clippedByChildren:(clipChildren := true).
+ ].
+
+ "Modified: 5.9.1995 / 12:25:25 / claus"
+
+
+!
+
+setDefaultActions
+
+ pressAction := [:pressPoint | self startSelectOrMove:pressPoint].
+ shiftPressAction := [:pressPoint | self startSelectMoreOrMove:pressPoint].
+ motionAction := [:movePoint | nil].
+ releaseAction := [nil].
+ keyPressAction := nil.
+
+ self cursor:Cursor normal.
+
+!
+
+showDragging:something offset:anOffset
+ "drag around a View"
+
+ |top|
+
+ self forEach:something do:[:anObject |
+ self drawRectangle:((anObject origin + anOffset) extent:(anObject extent))
+ ]
+
+! !
+
+!UIObjectView methodsFor:'object creation'!
+
+XXstartCreate:aPoint
+ "start a widget create
+ "
+ |widget object start frame delta|
+
+ (createClass isNil or:[self numberOfSelections > 1]) ifTrue:[
+ self unselect.
+ ^ self setDefaultActions.
+ ].
+
+ motionAction := [:movePoint| self doDragCreate:movePoint].
+ releaseAction := [ self endCreate].
+
+ widget := self singleSelection.
+
+ ( widget notNil
+ and:[(self isPoint:aPoint containedIn:widget)
+ and:[self supportsSubComponents:widget]]
+ ) ifFalse:[
+ self unselect.
+ widget := self.
+ ].
+
+ object := createClass new.
+ widget addSubView:object.
+
+ start := self alignToGrid:aPoint.
+ delta := widget originRelativeTo:self.
+ frame := Rectangle origin:(start - delta) corner:start.
+
+ object origin:(frame origin).
+ self setupCreatedObject:object.
+ object realize.
+
+ self actionCreate:object frame:frame delta:delta.
+ self invertOutlineOf:object.
+
+
+!
+
+actionCreate:anObject frame:aFrame delta:aDelta
+ "create and initialize action data
+ "
+ |extent x y selectors values|
+
+"minimum extent
+"
+ extent := self extent.
+ x := extent x // 3.
+ y := extent y // 3.
+ extent := anObject preferredExtent.
+
+ (extent x > x) ifTrue:[extent x:x].
+ (extent y > y) ifTrue:[extent y:y].
+
+"setup structure
+"
+ selectors := #( object frame delta vertical horizontal minExtent ).
+ values := Array new:(selectors size).
+
+ values at:1 put:anObject.
+ values at:2 put:aFrame.
+ values at:3 put:aDelta.
+ values at:4 put:(self isVerticalResizable:anObject).
+ values at:5 put:(self isHorizontalResizable:anObject).
+ values at:6 put:extent.
+
+ actionData := Structure newWith:selectors values:values.
+
+
+"can change cursor dependent on vertical/horizontal resizing
+"
+ oldCursor := cursor.
+ self cursor:(Cursor leftHand).
+
+
+
+!
+
+createWidgetWithClass:aClass
+ "prepare to create new widgets
+ "
+ createClass := aClass.
+ pressAction := [:pressPoint | self startCreate:pressPoint].
+ self cursor:Cursor origin.
+
+
+!
+
+doDragCreate:aPoint
+ "do a widget create drag
+ "
+ |frame object extent minimum|
+
+ frame := actionData frame.
+ frame corner:((self alignToGrid:aPoint) - (actionData delta)).
+
+ object := actionData object.
+ minimum := actionData minExtent.
+ extent := frame extent.
+
+ ((extent x < minimum x) or:[actionData horizontal not]) ifTrue:[
+ extent x:(minimum x)
+ ].
+
+ ((extent y < minimum y) or:[actionData vertical not]) ifTrue:[
+ extent y:(minimum y)
+ ].
+
+ frame extent:extent.
+
+ self invertOutlineOf:object.
+ object origin:(frame origin) extent:(frame extent).
+ self invertOutlineOf:object.
+!
+
+endCreate
+ "end a widget create drag
+ "
+ |layout x y object|
+
+ object := actionData object.
+ self invertOutlineOf:object.
+ inputView raise.
+
+ object superView specClass basicNew setupInitialLayoutFor:object.
+
+ self changed:#tree.
+ self select:object.
+ actionData := nil.
+
+ self setDefaultActions.
+
+!
+
+setupCreatedObject:anObject
+ self subclassResponsibility
+!
+
+startCreate:aPoint
+ "start a widget create
+ "
+ |widget object start frame delta|
+
+ (createClass isNil or:[self numberOfSelections > 1]) ifTrue:[
+ self unselect.
+ ^ self setDefaultActions.
+ ].
+
+ (widget := self singleSelection) notNil ifTrue:[
+ self unselect.
+
+ (self isPoint:aPoint containedIn:widget) ifFalse:[
+ widget := self
+ ] ifTrue:[
+ (self supportsSubComponents:widget) ifFalse:[
+ ^ self setDefaultActions.
+ ]
+ ]
+ ] ifFalse:[
+ widget := self
+ ].
+
+ motionAction := [:movePoint| self doDragCreate:movePoint].
+ releaseAction := [ self endCreate].
+
+ object := createClass new.
+ widget addSubView:object.
+
+ start := self alignToGrid:aPoint.
+ delta := widget originRelativeTo:self.
+ frame := Rectangle origin:(start - delta) corner:start.
+
+ object origin:(frame origin).
+ self setupCreatedObject:object.
+ object realize.
+
+ self actionCreate:object frame:frame delta:delta.
+ self invertOutlineOf:object.
+
+
+! !
+
+!UIObjectView methodsFor:'object moving'!
+
+doObjectMove:aPoint
+ "move selection
+ "
+ movedObject notNil ifTrue:[
+ movedObject keysAndValuesDo:[:nr :aView|
+ self invertOutlineOf:aView.
+ self moveObject:aView to:(aPoint - (moveDelta at:nr)).
+ self invertOutlineOf:aView.
+ ]
+ ]
+
+!
+
+endObjectMove
+ "cleanup after object move"
+
+ movedObject notNil ifTrue:[
+ movedObject do:[:aView|
+ self invertOutlineOf:aView
+ ].
+
+ movedObject do:[:aView|
+ self showSelected:aView
+ ].
+ movedObject size == 1 ifTrue:[
+ selection := movedObject at:1
+ ] ifFalse:[
+ selection := movedObject
+ ].
+
+ movedObject := nil.
+ self setDefaultActions.
+ self changed:#layout.
+ ].
+!
+
+moveObject:anObject to:aPoint
+ "move anObject to newOrigin, aPoint
+ "
+ |dX dY org delta|
+
+ anObject notNil ifTrue:[
+ org := anObject computeOrigin.
+
+ delta := aPoint - org.
+ delta := (self alignToGrid:aPoint) - org.
+ dX := delta x.
+ dY := delta y.
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:anObject top:dY bottom:dY left:dX right:dX
+ ]
+ ]
+
+!
+
+startObjectMoveAt:aPoint
+
+ self startObjectMove:selection at:aPoint.
+
+ selection isCollection ifTrue:[
+ movedObject := selection
+ ] ifFalse:[
+ movedObject := Array with:selection
+ ].
+ super unselect.
+
+ moveDelta := movedObject collect:[:aView|
+ aPoint - aView computeOrigin
+ ].
+
+ self transaction:#move objects:movedObject do:[:aView|
+ self invertOutlineOf:aView.
+ self undoLayoutView:aView
+ ].
+
+!
+
+startSelectMoreOrMove:aPoint
+ "add/remove to/from selection"
+
+ |anObject|
+
+ testMode ifTrue:[^ self].
+
+ anObject := self findObjectAt:aPoint.
+ anObject notNil ifTrue:[
+ (self isSelected:anObject) ifTrue:[
+ self removeFromSelection:anObject
+ ] ifFalse:[
+ self addToSelection:anObject
+ ]
+ ]
+!
+
+startSelectOrMove:aPoint
+ "a button is pressed at a point
+ "
+ |anObject b|
+
+ testMode ifTrue:[^ self].
+
+ "if there is one object selected and point hits a handle, start a resize
+ "
+ anObject := self singleSelection.
+
+ anObject notNil ifTrue:[
+ b := self whichHandleOf:anObject isHitBy:aPoint.
+
+ (b notNil and:[b ~~ #view]) ifTrue:[
+ ^ self startResizeBorder:b of:anObject.
+ ]
+ ].
+
+ anObject := self findObjectAt:aPoint.
+
+ "nothing is selected
+ "
+ anObject isNil ifTrue:[
+ ^ self unselect
+ ].
+
+ (self isSelected:anObject) ifFalse:[
+ super unselect.
+ self select:anObject.
+ ].
+
+ (self numberOfSelections ~~ 1) ifTrue:[
+ releaseAction := [
+ self setDefaultActions.
+ self select:anObject
+ ]
+ ] ifFalse:[
+ releaseAction := [self setDefaultActions]
+ ].
+
+ "prepare move operation for an object
+ "
+ motionAction := [:movePoint|
+ (aPoint dist:movePoint) > 4.0 ifTrue:[
+ self startObjectMoveAt:aPoint
+ ]
+ ].
+! !
+
+!UIObjectView methodsFor:'object resize'!
+
+actionResize:anObject selector:aSelector
+ "create and initialize action for resize
+ "
+ |selector delta|
+
+ delta := anObject container originRelativeTo:self.
+ selector := ('resize:', aSelector, ':') asSymbol.
+
+ actionData := Structure with:(#object->anObject)
+ with:(#selector->selector)
+ with:(#delta->delta).
+
+"can change cursor dependent on vertical/horizontal resizing
+"
+ oldCursor := cursor.
+ self cursor:(Cursor leftHand).
+
+
+
+!
+
+doDragResize:aPoint
+ "do a widget resize drag"
+
+ |p object|
+
+ object := actionData object.
+
+ self invertOutlineOf:object.
+ p := (self alignToGrid:aPoint) - (actionData delta).
+ self perform:(actionData selector) with:object with:p.
+ object geometryLayout:(object geometryLayout).
+ self invertOutlineOf:object
+
+!
+
+endResize
+ "cleanup after object resize"
+
+ self invertOutlineOf:(actionData object).
+ self setDefaultActions.
+ super select:(actionData object).
+ self changed:#layout.
+ actionData := nil
+!
+
+startResizeBorder:b of:selection
+ "resize selected view
+ "
+ |object|
+
+ object := self singleSelection.
+
+ (object geometryLayout) isNil ifTrue:[
+ ^ self setDefaultActions.
+ ].
+
+ self actionResize:object selector:b.
+
+ self transaction:#extent selectionDo:[:aView|
+ self undoLayoutView:aView
+ ].
+ super unselect.
+
+ motionAction := [:movePoint | self doDragResize:movePoint].
+ releaseAction := [self endResize].
+ self invertOutlineOf:object
+! !
+
+!UIObjectView methodsFor:'private handles'!
+
+handlesOf:aComponent do:aBlock
+ |delta layout vertical horizontal|
+
+ layout := aComponent geometryLayout.
+ delta := (aComponent originRelativeTo:self) - aComponent origin.
+
+ (layout isLayout not or:[layout isLayoutFrame]) ifTrue:[
+ vertical := self isVerticalResizable:aComponent.
+ horizontal := self isHorizontalResizable:aComponent.
+ ] ifFalse:[
+ vertical := false.
+ horizontal := false.
+ ].
+
+ horizontal ifTrue:[
+ aBlock value:(aComponent leftCenter + delta) value:#left.
+ aBlock value:(aComponent rightCenter + delta) value:#right.
+ ].
+
+ vertical ifTrue:[
+ aBlock value:(aComponent topCenter + delta) value:#top.
+ aBlock value:(aComponent bottomCenter + delta) value:#bottom.
+ ].
+
+ (horizontal and:[vertical]) ifTrue:[
+ aBlock value:(aComponent origin + delta) value:#origin.
+ aBlock value:(aComponent corner + delta) value:#corner.
+ aBlock value:(aComponent topRight + delta) value:#topRight.
+ aBlock value:(aComponent bottomLeft + delta) value:#bottomLeft.
+ ] ifFalse:[
+ aBlock value:(aComponent origin + delta) value:#view.
+ aBlock value:(aComponent corner + delta) value:#view.
+ aBlock value:(aComponent topRight + delta) value:#view.
+ aBlock value:(aComponent bottomLeft + delta) value:#view.
+ ].
+
+!
+
+showSelected:aComponent
+ |wasClipped delta oldPaint|
+
+ self paint:Color black.
+
+ (wasClipped := clipChildren) ifTrue:[
+ self clippedByChildren:(clipChildren := false).
+ ].
+
+ self handlesOf:aComponent do:[:pnt :what |
+ what == #view ifTrue:[self displayRectangle:(pnt - (4@4) extent:7@7)]
+ ifFalse:[self fillRectangle:(pnt - (4@4) extent:7@7)]
+ ].
+
+ wasClipped ifTrue:[
+ self clippedByChildren:(clipChildren := true).
+ ].
+ self paint:oldPaint.
+!
+
+showUnselected:aComponent
+ |wasClipped delta r oldPaint|
+
+ r := aComponent origin extent:8@8.
+
+ (wasClipped := clipChildren) ifTrue:[
+ self clippedByChildren:(clipChildren := false).
+ ].
+
+ self handlesOf:aComponent do:[:pnt :what |
+ self clearRectangle:(pnt - (4@4) extent:7@7).
+ ].
+
+ wasClipped ifTrue:[
+ self clippedByChildren:(clipChildren := true).
+ ].
+
+ "/ must redraw all components which are affected b the handles
+
+ r := (aComponent originRelativeTo:self) - (4@4)
+ extent:(aComponent extent + (4@4)).
+
+ subViews do:[:anotherComponent |
+ |absOrg absFrame|
+
+ anotherComponent ~~ inputView ifTrue:[
+ absOrg := anotherComponent originRelativeTo:self.
+ absFrame := absOrg extent:(anotherComponent extent).
+ (absFrame intersects:r) ifTrue:[
+ anotherComponent withAllSubViewsDo:[:v |
+ v clear.
+ v exposeX:0 y:0 width:9999 height:9999.
+ ]
+ ]
+ ]
+ ]
+
+!
+
+whichHandleOf:aView isHitBy:aPoint
+ |bounds|
+
+ self handlesOf:aView do:[:pnt :what |
+ ((pnt - (4@4) extent:7@7) containsPoint:aPoint) ifTrue:[
+ ^ what
+ ].
+ ].
+
+ ^ nil
+
+ "Modified: 5.9.1995 / 14:39:34 / claus"
+
+! !
+
+!UIObjectView methodsFor:'private resizing-subviews'!
+
+resize:aView bottom:aPoint
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView top:0 bottom:((aPoint y) - (aView computeCorner y))
+ ]
+!
+
+resize:aView bottomLeft:aPoint
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView top:0
+ bottom:((aPoint y) - (aView computeCorner y))
+ left:((aPoint x) - (aView computeOrigin x))
+ right:0
+
+ ]
+
+
+!
+
+resize:aView corner:aPoint
+ |delta|
+
+ delta := aPoint - aView computeCorner.
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView top:0 bottom:(delta y) left:0 right:(delta x)
+ ]
+!
+
+resize:aView left:aPoint
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView left:((aPoint x) - (aView computeOrigin x)) right:0
+ ]
+
+!
+
+resize:aView origin:aPoint
+ |delta|
+
+ delta := aPoint - aView computeOrigin.
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView top:(delta y) bottom:0 left:(delta x) right:0
+ ]
+
+!
+
+resize:aView right:aPoint
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView left:0 right:((aPoint x) - (aView computeCorner x))
+ ]
+!
+
+resize:aView top:aPoint
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView top:((aPoint y) - (aView computeOrigin y)) bottom:0
+ ]
+!
+
+resize:aView topRight:aPoint
+
+ undoHistory disabledTransitionDo:[
+ self shiftLayout:aView top:((aPoint y) - (aView computeOrigin y))
+ bottom:0
+ left:0
+ right:((aPoint x) - (aView computeCorner x))
+
+ ]
+
+! !
+
+!UIObjectView methodsFor:'private shift-layout'!
+
+shiftLayout:aView left:l right:r
+ "shift layout for a view; in case of an open transaction, the undo
+ action is registered
+ "
+ self shiftLayout:aView top:0 bottom:0 left:l right:r
+
+!
+
+shiftLayout:aView top:t bottom:b
+ "shift layout for a view; in case of an open transaction, the undo
+ action is registered
+ "
+ self shiftLayout:aView top:t bottom:b left:0 right:0
+
+
+!
+
+shiftLayout:aView top:t bottom:b left:l right:r
+ "shift layout for a view; in case of an open transaction, the undo
+ action is registered
+ "
+ |layout|
+
+ layout := aView geometryLayout.
+
+ self undoLayoutView:aView.
+
+ layout isLayout ifTrue:[
+ layout leftOffset:(layout leftOffset + l)
+ topOffset:(layout topOffset + t).
+
+ layout isLayoutFrame ifTrue:[
+ layout bottomOffset:(layout bottomOffset + b).
+ layout rightOffset:(layout rightOffset + r).
+ ].
+ aView geometryLayout:layout.
+ ] ifFalse:[
+ |pixelOrigin|
+
+ pixelOrigin := aView pixelOrigin.
+ pixelOrigin := pixelOrigin + (l@t).
+ aView pixelOrigin:pixelOrigin
+ ]
+
+
+! !
+
+!UIObjectView methodsFor:'searching'!
+
+findObjectAt:aPoint
+ "find the origin/corner of the currentWidget
+ "
+ |view viewId lastId point listOfViews|
+
+ viewId := rootView id.
+ point := aPoint + (device translatePoint:0@0 from:(self id) to:viewId).
+
+ inputView lower.
+
+ [viewId notNil] whileTrue:[
+ lastId := viewId.
+ viewId := device viewIdFromPoint:point in:lastId
+ ].
+
+ inputView raise.
+
+ view := device viewFromId:lastId.
+
+ view ~~ inputView ifTrue:[^ view].
+
+ "/ look for 'hidden' views ...
+
+ listOfViews := OrderedCollection new.
+ self allSubViewsDo:[:aView |
+ |org|
+
+ aView ~~ inputView ifTrue:[
+ org := device translatePoint:0@0 from:(aView id) to:self id.
+ ((org extent:aView extent) containsPoint:aPoint) ifTrue:[
+ listOfViews add:aView.
+ ]
+ ]
+ ].
+
+ listOfViews size > 0 ifTrue:[
+ ^ listOfViews last
+ ].
+ ^ nil
+
+
+!
+
+isPoint:aPoint containedIn:aView
+ "checks whether a point is covered by a view.
+ "
+ |p|
+
+ p := device translatePoint:aPoint from:inputView id to:aView id.
+
+ (p x >= 0 and:[p y >= 0]) ifTrue:[
+ p := aView extent - p.
+
+ (p x >= 0 and:[p y >= 0]) ifTrue:[
+ ^ true
+ ]
+ ].
+ ^ false
+!
+
+whichBorderOf:aView isHitBy:aPoint
+ |p r bw org|
+
+ bw := aView borderWidth.
+ p := aPoint - (aView superView originRelativeTo:self).
+
+ r := Rectangle origin:(aView origin)
+ extent:(aView width @ bw).
+ (r containsPoint:p) ifTrue:[^ #top:].
+
+ r origin:(aView left @ (aView bottom + bw)) extent:(aView width @ bw).
+ (r containsPoint:p) ifTrue:[^ #bottom:].
+
+ r top:(aView top).
+ r extent:(bw @ aView height).
+ (r containsPoint:p) ifTrue:[^ #left:].
+
+ r origin:((aView right + bw) @ aView top).
+ (r containsPoint:p) ifTrue:[^ #right:].
+
+ ^ nil
+
+
+! !
+
+!UIObjectView methodsFor:'selections'!
+
+addToSelection:something
+ (self canSelect:something) ifTrue:[
+ super addToSelection:something.
+ self changed:#selection.
+ ]
+!
+
+inspectSelection
+ self singleSelectionDo:[:aView |
+ aView inspect
+ ]
+!
+
+numberOfSelections
+ "return the number of selected entries"
+
+ |sz|
+
+ selection isNil ifTrue:[^ 0].
+
+ selection isCollection ifTrue:[^ selection size]
+ ifFalse:[^ 1 ]
+!
+
+removeFromSelection:something
+ super removeFromSelection:something.
+ self changed:#selection
+
+!
+
+select:something
+ (self canSelect:something) ifTrue:[
+ super select:something.
+ self changed:#selection
+ ]
+
+!
+
+selection
+ ^ selection
+
+
+!
+
+selectionHiddenDo:aBlock
+ "apply block to every object in selection"
+
+ self selectionDo:[:aView |
+ self showUnselected:aView.
+ ].
+ device flush.
+ aBlock value.
+ self selectionDo:[:aView |
+ self showSelected:aView
+ ]
+
+
+!
+
+singleSelection
+ "returns single selection or nil
+ "
+ selection isCollection ifFalse:[
+ ^ selection
+ ].
+ selection size == 1 ifTrue:[ ^ selection at:1]
+ ifFalse:[ ^ nil].
+!
+
+singleSelectionDo:aBlock
+ |view|
+
+ (view := self singleSelection) notNil ifTrue:[
+ aBlock value:view
+ ]
+!
+
+unselect
+ selection notNil ifTrue:[
+ super unselect.
+ self changed:#selection
+ ]
+
+!
+
+withSelectionHiddenDo:aBlock
+ "evaluate aBlock while selection is hidden"
+
+ |sel|
+
+ selection isNil ifTrue:[
+ aBlock value
+ ] ifFalse:[
+ sel := selection.
+ super unselect.
+ aBlock value.
+ super select:sel
+ ]
+
+ "Modified: 6.9.1995 / 01:46:16 / claus"
+
+
+! !
+
+!UIObjectView methodsFor:'testing'!
+
+canMove:something
+ ^ true
+
+
+!
+
+canPaste:something
+ "returns true if something could be paste
+ "
+ something notNil ifTrue:[
+ something isCollection ifTrue:[
+ something notEmpty ifTrue:[
+ ^ (something at:1) isKindOf:UISpecification
+ ]
+ ] ifFalse:[
+ ^ something isKindOf:UISpecification
+ ]
+ ].
+ ^ false
+
+!
+
+canSelect:something
+ ^ (testMode not and:[something ~~ selection])
+
+!
+
+hasUndos
+ "returns true if undoHistory not empty
+ "
+ ^ undoHistory notEmpty
+!
+
+isHorizontalResizable:aComponent
+ ^ self subclassResponsibility
+
+
+!
+
+isVerticalResizable:aComponent
+ ^ self subclassResponsibility
+
+
+!
+
+supportsSubComponents:something
+ "returns true if somrthing supports subcomponents
+ "
+ |specClass|
+
+ something notNil ifTrue:[
+ something isCollection ifFalse:[
+ specClass := something specClass
+ ] ifTrue:[
+ something size == 1 ifTrue:[
+ specClass := (something at:1) specClass
+ ]
+ ].
+ specClass notNil ifTrue:[
+ ^ specClass basicNew supportsSubComponents
+ ]
+ ].
+ ^ false
+! !
+
+!UIObjectView methodsFor:'transaction'!
+
+transaction:aType objects:something do:aOneArgBlock
+ "opens a transaction and evaluates a block within the transaction; the
+ argument to the block is a view from derived from something
+ "
+ self subclassResponsibility
+
+
+!
+
+transaction:aType selectionDo:aOneArgBlock
+ "opens a transaction and evaluates a block within the transaction; the
+ argument to the block is a view from the selection
+ "
+ self transaction:aType objects:selection do:aOneArgBlock
+
+
+!
+
+undoLayoutView:aView
+ "prepare undo action for a view changing its layout
+ "
+ self subclassResponsibility
+
+! !
+
+!UIObjectView methodsFor:'user actions - arrange'!
+
+lowerSelection
+ self selectionDo:[:aView| aView lower ].
+
+
+!
+
+raiseSelection
+ self selectionDo:[:aView| aView raise ].
+ inputView raise.
+
+
+! !
+
+!UIObjectView methodsFor:'user actions - dimension'!
+
+copyExtent
+ |object|
+
+ object := self singleSelection.
+
+ object notNil ifTrue:[
+ copiedExtent := object computeExtent
+ ] ifFalse:[
+ self warn:'exactly one element must be selected'.
+ ]
+
+
+
+!
+
+pasteExtent
+ copiedExtent notNil ifTrue:[
+ self transition:#extent dimensionDo:[:v|
+ self resize:v corner:(v computeOrigin + copiedExtent)
+ ]
+ ]
+!
+
+pasteHeight
+ copiedExtent notNil ifTrue:[
+ self transition:'paste height' dimensionDo:[:v|
+ self resize:v bottom:(v computeOrigin + copiedExtent)
+ ]
+ ]
+
+!
+
+pasteWidth
+ copiedExtent notNil ifTrue:[
+ self transition:'paste width' dimensionDo:[:v|
+ self resize:v right:(v computeOrigin + copiedExtent)
+ ]
+ ]
+
+!
+
+setDimension:aLayout
+ |type|
+
+ aLayout isLayout ifTrue:[
+ aLayout isLayoutFrame ifTrue:[
+ type := #layoutFrame
+ ] ifFalse:[
+ aLayout isAlignmentOrigin ifTrue:[
+ type := #layoutAlignOrigin.
+ ] ifFalse:[
+ type := #layoutOrigin
+ ]
+ ]
+ ] ifFalse:[
+ type := #layout
+ ].
+
+ self transition:type dimensionDo:[:v| v geometryLayout:(aLayout copy)]
+
+!
+
+setToDefaultExtent
+ self transition:#extent dimensionDo:[:v|
+ self resize:v corner:(v computeOrigin + (v preferredExtent))
+ ]
+
+!
+
+setToDefaultHeight
+ self transition:'default height' dimensionDo:[:v|
+ self resize:v bottom:(v computeOrigin + (v preferredExtent))
+ ]
+
+!
+
+setToDefaultWidth
+ self transition:'default width' dimensionDo:[:v|
+ self resize:v right:(v computeOrigin + (v preferredExtent))
+ ]
+
+!
+
+transition:aType dimensionDo:aOneArgBlock
+ "change dimension within a transaction for the selected elements by evaluating
+ the block with the argument a view.
+ "
+ self selectionHiddenDo:[
+ self transaction:aType selectionDo:[:aView|
+ self undoLayoutView:aView.
+ aOneArgBlock value:aView.
+ ].
+ self changed:#layout
+ ]
+! !
+
+!UIObjectView methodsFor:'user actions - move'!
+
+moveSelectionDown:aNumber
+ |gridY|
+
+ gridAlign notNil ifTrue:[
+ gridY := gridAlign y.
+ ].
+
+ self selectionHiddenDo:[
+ self transaction:#move selectionDo:[:aView|
+ |n d|
+
+ n := aNumber.
+
+ aligning ifTrue:[
+ d := ((aView computeCorner y) \\ gridY).
+ n := n * gridY.
+
+ d ~~ 0 ifTrue:[
+ n := n - d + 1.
+ ]
+ ].
+ self shiftLayout:aView top:n bottom:n
+ ].
+ self changed:#layout
+ ]
+
+
+!
+
+moveSelectionLeft:aNumber
+ "move selection left
+ "
+ |gridX|
+
+ gridAlign notNil ifTrue:[
+ gridX := gridAlign x.
+ ].
+
+ self selectionHiddenDo:[
+ self transaction:#move selectionDo:[:aView|
+ |n d|
+
+ n := aNumber.
+
+ aligning ifTrue:[
+ d := ((aView computeOrigin x) \\ gridX).
+ d ~~ 0 ifTrue:[
+ n := n-1.
+ ].
+ n := (n * gridX) + d.
+ ].
+ n := n negated.
+ self shiftLayout:aView left:n right:n
+
+ ].
+ self changed:#layout
+ ]
+!
+
+moveSelectionRight:aNumber
+ "move selection right
+ "
+ |gridX|
+
+ gridAlign notNil ifTrue:[
+ gridX := gridAlign x.
+ ].
+
+ self selectionHiddenDo:[
+ self transaction:#move selectionDo:[:aView|
+ |n d|
+
+ n := aNumber.
+
+ aligning ifTrue:[
+ d := ((aView computeCorner x) \\ gridX).
+ n := n * gridX.
+
+ d ~~ 0 ifTrue:[
+ n := n - d + 1.
+ ]
+ ].
+ self shiftLayout:aView left:n right:n
+
+ ].
+ self changed:#layout
+ ]
+!
+
+moveSelectionUp:aNumber
+ "move selection up
+ "
+ |gridY|
+
+ gridAlign notNil ifTrue:[
+ gridY := gridAlign y.
+ ].
+
+ self selectionHiddenDo:[
+ self transaction:#move selectionDo:[:aView|
+ |n d|
+
+ n := aNumber.
+
+ aligning ifTrue:[
+ d := ((aView computeOrigin x) \\ gridY).
+ d ~~ 0 ifTrue:[
+ n := n-1.
+ ].
+ n := (n * gridY) + d.
+ ].
+ n := n negated.
+ self shiftLayout:aView top:n bottom:n
+ ].
+ self changed:#layout
+ ]
+
+
+! !
+
+!UIObjectView methodsFor:'user actions - position'!
+
+alignSelectionBottom
+ |bmost delta layout|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ self numberOfSelections > 1 ifTrue:[
+ bmost := (selection at:1) computeCorner y.
+
+ self transaction:#align selectionDo:[:v|
+ (delta := bmost - (v computeCorner y)) ~~ 0 ifTrue:[
+ self shiftLayout:v top:delta bottom:delta.
+ ]
+ ]
+ ] ifFalse:[
+ layout := selection geometryLayout.
+
+ (layout isLayout and:[layout isLayoutFrame]) ifFalse:[
+ ^ self
+ ].
+
+ self transaction:#layout selectionDo:[:aView|
+ self undoLayoutView:aView.
+ layout := aView geometryLayout.
+ layout bottomOffset:0.
+ layout bottomFraction:1.0.
+ aView geometryLayout:layout.
+ ]
+ ]
+ ].
+ self changed:#layout
+ ]
+
+
+
+!
+
+alignSelectionCenterHor
+ |view center|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ view := self singleSelection.
+
+ view notNil ifTrue:[
+ view := view superView.
+ center := view computeExtent
+ ] ifFalse:[
+ view := selection at:1.
+ center := view computeCorner + view computeOrigin.
+ ].
+ center := center x // 2.
+
+ self transaction:#align selectionDo:[:v|
+ |newX oldX delta|
+
+ oldX := v computeOrigin x.
+ newX := center - ((v computeCorner x - oldX) // 2).
+ delta := newX - oldX.
+
+ self shiftLayout:v left:delta right:delta
+ ].
+ self changed:#layout
+ ]
+ ]
+
+
+
+!
+
+alignSelectionCenterVer
+ |view center|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ view := self singleSelection.
+
+ view notNil ifTrue:[
+ view := view superView.
+ center := view computeExtent
+ ] ifFalse:[
+ view := selection at:1.
+ center := view computeCorner + view computeOrigin.
+ ].
+ center := center y // 2.
+
+ self transaction:#align selectionDo:[:v|
+ |newY oldY delta|
+
+ oldY := v computeOrigin y.
+ newY := center - ((v computeCorner y - oldY) // 2).
+ delta := newY - oldY.
+
+ self shiftLayout:v top:delta bottom:delta
+ ].
+ self changed:#layout
+ ]
+ ]
+!
+
+alignSelectionLeft
+ |lmost delta layout|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ self numberOfSelections > 1 ifTrue:[
+ lmost := (selection at:1) computeOrigin x.
+
+ self transaction:#align selectionDo:[:v|
+ (delta := lmost - (v computeOrigin x)) ~~ 0 ifTrue:[
+ self shiftLayout:v left:delta right:delta
+ ]
+ ]
+ ] ifFalse:[
+ self transaction:#layout selectionDo:[:aView|
+ layout := aView geometryLayout.
+
+ layout isLayout ifTrue:[
+ self undoLayoutView:aView.
+ layout leftOffset:0.
+ layout leftFraction:0.0.
+ aView geometryLayout:layout.
+ ]
+ ]
+ ]
+ ].
+ self changed:#layout
+ ]
+!
+
+alignSelectionLeftAndRight
+ |lmost rmost layout|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ self numberOfSelections > 1 ifTrue:[
+ lmost := (selection at:1) computeOrigin x.
+ rmost := (selection at:1) computeCorner x.
+
+ self transaction:#align selectionDo:[:v|
+ self shiftLayout:v left:(lmost - (v computeOrigin x))
+ right:(rmost - (v computeCorner x))
+ ]
+ ] ifFalse:[
+ self transaction:#layout selectionDo:[:aView|
+ layout := aView geometryLayout.
+
+ layout isLayout ifTrue:[
+ self undoLayoutView:aView.
+ layout leftOffset:0.
+ layout leftFraction:0.0.
+
+ (layout isLayout and:[layout isLayoutFrame]) ifTrue:[
+ layout rightOffset:0.
+ layout rightFraction:1.0.
+ ].
+ aView geometryLayout:layout.
+ ]
+ ]
+ ]
+ ].
+ self changed:#layout
+ ]
+!
+
+alignSelectionRight
+ |rmost delta layout|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ self numberOfSelections > 1 ifTrue:[
+ rmost := (selection at:1) computeCorner x.
+
+ self transaction:#align selectionDo:[:v|
+ (delta := rmost - (v computeCorner x)) ~~ 0 ifTrue:[
+ self shiftLayout:v left:delta right:delta
+ ]
+ ]
+ ] ifFalse:[
+ layout := selection geometryLayout.
+
+ (layout isLayout and:[layout isLayoutFrame]) ifFalse:[
+ ^ self
+ ].
+
+ self transaction:#layout selectionDo:[:aView|
+ self undoLayoutView:aView.
+ layout := aView geometryLayout.
+ layout rightOffset:0.
+ layout rightFraction:1.0.
+ aView geometryLayout:layout.
+ ]
+ ]
+ ].
+ self changed:#layout
+ ]
+!
+
+alignSelectionTop
+ |tmost delta layout|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ self numberOfSelections > 1 ifTrue:[
+ tmost := (selection at:1) computeOrigin y.
+
+ self transaction:#align selectionDo:[:v|
+ (delta := tmost - (v computeOrigin y)) ~~ 0 ifTrue:[
+ self shiftLayout:v top:delta bottom:delta
+ ]
+ ]
+ ] ifFalse:[
+ self transaction:#layout selectionDo:[:aView|
+ layout := aView geometryLayout.
+
+ layout isLayout ifTrue:[
+ self undoLayoutView:aView.
+ layout topOffset:0.
+ layout topFraction:0.0.
+ aView geometryLayout:layout.
+ ]
+ ]
+ ]
+ ].
+ self changed:#layout
+ ]
+
+!
+
+alignSelectionTopAndBottom
+ |tmost bmost layout|
+
+ selection notNil ifTrue:[
+ self selectionHiddenDo:[
+ self numberOfSelections > 1 ifTrue:[
+ tmost := (selection at:1) computeOrigin y.
+ bmost := (selection at:1) computeCorner y.
+
+ self transaction:#align selectionDo:[:v|
+ self shiftLayout:v top:(tmost - (v computeOrigin y))
+ bottom:(bmost - (v computeCorner y))
+ ]
+ ] ifFalse:[
+ self transaction:#layout selectionDo:[:aView|
+ layout := aView geometryLayout.
+
+ layout isLayout ifTrue:[
+ self undoLayoutView:aView.
+ layout topOffset:0.
+ layout topFraction:0.0.
+
+ (layout isLayout and:[layout isLayoutFrame]) ifTrue:[
+ layout bottomOffset:0.
+ layout bottomFraction:1.0.
+ ].
+ aView geometryLayout:layout.
+ ]
+ ]
+ ]
+ ].
+ self changed:#layout
+ ]
+!
+
+centerSelection:aOneArgBlockXorY orientation:orientation
+ "center selection horizontal or vertical dependant on the block result( x or y).
+ The argument to the block is the point.
+ "
+ |superview min max delta val|
+
+ self selectionHiddenDo:[
+ max := 0.
+
+ self selectionDo:[:aView |
+ superview isNil ifTrue:[
+ superview := aView superView
+ ] ifFalse:[
+ (aView superView == superview) ifFalse:[
+ ^ self notify:'views must have same superview'.
+ ]
+ ].
+ val := aOneArgBlockXorY value:(aView computeOrigin).
+
+ min isNil ifTrue:[min := val]
+ ifFalse:[min := min min:val].
+
+ val := aOneArgBlockXorY value:(aView computeCorner).
+ max := max max:val.
+ ].
+
+ val := aOneArgBlockXorY value:(superview computeExtent).
+ max := (min + val - max) // 2.
+
+ max == min ifFalse:[
+ delta := max - min.
+
+ self transaction:#center selectionDo:[:v|
+ orientation == #y ifTrue:[
+ self shiftLayout:v top:delta bottom:delta
+ ] ifFalse:[
+ self shiftLayout:v left:delta right:delta
+ ]
+ ].
+ self changed:#layout
+ ]
+ ]
+
+
+!
+
+centerSelectionHor
+ "center selection horizontal
+ "
+ self centerSelection:[:aPoint| aPoint x] orientation:#x
+
+
+!
+
+centerSelectionVer
+ "center selection vertical
+ "
+ self centerSelection:[:aPoint| aPoint y] orientation:#y
+!
+
+spreadSelectionHor
+ |sumWidths min max viewsInOrder topsInOrder count space|
+
+ (self numberOfSelections > 1) ifFalse:[
+ ^ self
+ ].
+
+ self selectionHiddenDo:[
+ count := 0.
+ sumWidths := 0.
+ max := 0.
+
+ self selectionDo:[:aView |
+ sumWidths := sumWidths + aView width.
+
+ min isNil ifTrue:[min := aView left]
+ ifFalse:[min := min min:(aView left)].
+
+ max := max max:(aView right).
+ count := count + 1
+ ].
+ viewsInOrder := Array withAll:selection.
+ topsInOrder := viewsInOrder collect:[:aView | aView left].
+ topsInOrder sortWith:viewsInOrder.
+
+ space := (((max - min) - sumWidths) / (count - 1)) rounded asInteger.
+
+ self transaction:#spread objects:viewsInOrder do:[:aView|
+ |delta|
+
+ delta := min - aView computeOrigin x.
+ self shiftLayout:aView left:delta right:delta.
+ min := min + aView computeExtent x + space
+ ].
+ self changed:#layout
+ ]
+
+!
+
+spreadSelectionVer
+ |sumHeights min max viewsInOrder topsInOrder count space|
+
+ (self numberOfSelections > 1) ifFalse:[
+ ^ self
+ ].
+
+ self selectionHiddenDo:[
+ count := 0.
+ sumHeights := 0.
+ max := 0.
+
+ self selectionDo:[:aView |
+ sumHeights := sumHeights + aView height.
+
+ min isNil ifTrue:[min := aView top]
+ ifFalse:[min := min min:(aView top)].
+
+ max := max max:(aView bottom).
+ count := count + 1
+ ].
+ viewsInOrder := Array withAll:selection.
+ topsInOrder := viewsInOrder collect:[:aView|aView top].
+ topsInOrder sortWith:viewsInOrder.
+
+ space := (((max - min) - sumHeights) / (count - 1)) rounded asInteger.
+
+ self transaction:#spread objects:viewsInOrder do:[:aView|
+ |delta|
+
+ delta := min - aView computeOrigin y.
+ self shiftLayout:aView top:delta bottom:delta.
+ min := min + aView height + space
+ ].
+ self changed:#layout
+ ]
+! !
+
+!UIObjectView methodsFor:'user actions - undo history'!
+
+openUndoMenu
+ self unselect.
+ undoHistory openUndoMenu.
+ self changed:#tree
+
+!
+
+removeUndoHistory
+ "delete total undo history
+ "
+ undoHistory reinitialize
+!
+
+undoLast
+ self unselect.
+ undoHistory undoLast:1.
+ self changed:#tree
+! !
+
+!UIObjectView::UndoHistory class methodsFor:'constants'!
+
+maxHistorySize
+ "returns maximum size of history before removing oldest
+ record
+ "
+ ^ 50
+
+
+! !
+
+!UIObjectView::UndoHistory class methodsFor:'instance creation'!
+
+new
+ ^ self basicNew initialize
+
+
+! !
+
+!UIObjectView::UndoHistory methodsFor:'accessing'!
+
+historySize
+ ^ history size
+! !
+
+!UIObjectView::UndoHistory methodsFor:'initialization'!
+
+initialize
+ super initialize.
+ self reinitialize.
+
+
+!
+
+reinitialize
+ "reinitialize all attributes
+ "
+ history := OrderedCollection new.
+ transaction := nil.
+ enabled := true.
+
+
+! !
+
+!UIObjectView::UndoHistory methodsFor:'menu'!
+
+openUndoMenu
+ |list top slv hzp inset selection okButton|
+
+ history isEmpty ifTrue:[
+ ^ self
+ ].
+
+ top := StandardSystemView new label:'undo history'; extent:250@350.
+ slv := ScrollableView for:SelectionInListView origin:0.0@0.0 corner:1.0@1.0 in:top.
+ hzp := HorizontalPanelView origin:0.0@1.0 corner:1.0@1.0 in:top.
+ hzp horizontalLayout:#fitSpace.
+
+ (Button abortButtonIn:hzp) action:[ selection := nil. top destroy ].
+ okButton := Button okButtonIn:hzp.
+ okButton label:'undo to end'.
+ okButton action:[ top destroy ].
+
+ inset := hzp preferredExtent y.
+ hzp topInset:(inset negated).
+ slv bottomInset:inset.
+ slv := slv scrolledView.
+
+ list := history collect:[:aTrans||e|
+ e := MultiColListEntry new.
+ e colAt:1 put:(aTrans type asString).
+ e colAt:2 put:(aTrans text ? '').
+ e
+ ].
+
+ slv list:list.
+ slv action:[:index | selection := index ].
+ top openModal.
+
+ selection notNil ifTrue:[
+ self undoLast:(history size - selection + 1).
+ ]
+! !
+
+!UIObjectView::UndoHistory methodsFor:'testing'!
+
+isEmpty
+ "returns true if undo history is empty
+ "
+ ^ history isEmpty
+
+
+!
+
+isTransactionOpen
+ ^ (enabled and:[transaction notNil])
+!
+
+notEmpty
+ "returns true if undo history is not empty
+ "
+ ^ history notEmpty
+
+
+! !
+
+!UIObjectView::UndoHistory methodsFor:'transaction'!
+
+addUndoBlock:anUndoBlock
+ "undo block to restore changes; add block to current transaction
+ "
+ self isTransactionOpen ifTrue:[
+ transaction add:anUndoBlock
+ ]
+
+
+!
+
+disabledTransitionDo:aBlock
+ "disable transitions during evaluating the block
+ "
+ |oldState|
+
+ oldState := enabled.
+ enabled := false.
+ aBlock value.
+ enabled := oldState.
+!
+
+transaction:aType do:aBlock
+ self transaction:aType text:nil do:aBlock
+!
+
+transaction:aType text:aTextOrNil do:aBlock
+ "open a transaction; perform the block; at least close the transaction
+ "
+ (enabled and:[transaction isNil]) ifTrue:[
+ transaction := Transaction type:aType text:aTextOrNil.
+
+ aBlock value.
+
+ transaction isEmpty ifFalse:[
+ history addLast:transaction.
+ history size > (self class maxHistorySize) ifTrue:[history removeFirst]
+ ].
+ transaction := nil
+
+ ] ifFalse:[
+ aBlock value
+ ]
+! !
+
+!UIObjectView::UndoHistory methodsFor:'undo'!
+
+undoLast:nTransactions
+ "undo last n transactions; an open transaction will be closed;
+ transactions during undo are disabled
+ "
+ |n|
+
+ transaction := nil.
+ n := nTransactions min:(history size).
+
+ n ~~ 0 ifTrue:[
+ enabled := false.
+ n timesRepeat:[ (history removeLast) undo ].
+ enabled := true.
+ ]
+
+
+! !
+
+!UIObjectView::UndoHistory::Transaction class methodsFor:'documentation'!
+
+version
+ ^ '$Header$'
+! !
+
!UIObjectView::UndoHistory::Transaction class methodsFor:'instance creation'!
type:aType text:aTextOrNil
@@ -98,3 +2173,8 @@
^ actions notNil
! !
+!UIObjectView class methodsFor:'documentation'!
+
+version
+ ^ '$Header$'
+! !