#FEATURE by cg
class: MethodFinderWindow
changed: #showMeHowItWorks
class: MethodFinderWindow class
changed: #windowSpec
"{ Package: 'stx:libtool2' }"
"{ NameSpace: Smalltalk }"
Object subclass:#ShowMeHowItWorks
instanceVariableNames:'opStream lastComponentName lastComponent'
classVariableNames:''
poolDictionaries:''
category:'Interface-Help'
!
!ShowMeHowItWorks class methodsFor:'documentation'!
documentation
"
documentation to be added.
class:
<a short class summary here, describing what instances represent>
responsibilities:
<describing what my main role is>
collaborators:
<describing with whom and how I talk to>
API:
<public api and main messages>
example:
<a one-line examples on how to use - can also be in a separate example method>
implementation:
<implementation points>
[author:]
Claus Gittinger
[instance variables:]
[class variables:]
[see also:]
"
!
example
ShowMeHowItWorks do:#(
( showing: 'Choose the number of arguments' do:(
moveTo: NumberOfArguments
select: '1'
))
(showing: 'Click into the "receiver" field' do:(
moveTo: ReceiverEditor
click: ReceiverEditor
))
(showing: 'Enter a value (or expression) into "receiver" field' do:(
enter: '100'
))
(showing: 'Click into the "first argument" field' do:(
moveTo: Arg1Editor
click: ReceiverEditor
))
(showing: 'Enter a value (or expression) into "receiver" field' do:(
enter: '100'
))
)
! !
!ShowMeHowItWorks class methodsFor:'running'!
do:specArray
self doStream:specArray readStream
"Created: / 19-07-2019 / 10:52:59 / Claus Gittinger"
"Modified: / 19-07-2019 / 14:30:43 / Claus Gittinger"
!
doStream:specStream
"must run as a separate process;
otherwise - if started by the app itself -
no events will be processed while running"
[
self new doStream:specStream
] fork.
"Created: / 19-07-2019 / 10:53:07 / Claus Gittinger"
"Modified (comment): / 19-07-2019 / 14:30:29 / Claus Gittinger"
! !
!ShowMeHowItWorks methodsFor:'commands'!
pause
<action>
Dialog information:'Show Paused.\Click on "OK" to proceed'
"Created: / 19-07-2019 / 15:03:17 / Claus Gittinger"
!
showing:message do:operations
"execute operations while showing (and speaking) some message."
<action>
|messageView talkDone|
self assert:operations isSequenceable.
messageView := ActiveHelpView for:message.
"/ messageView shapeStyle:#cartoon.
[
messageView realize.
self talking ifTrue:[
talkDone := Semaphore new.
[
self tell:message.
talkDone signal
] fork.
"/
"/ allow speaker some headoff
Delay waitForSeconds:(message size / 20).
].
self doStream:(operations readStream).
] ensure:[
messageView destroy
].
self talking ifTrue:[
talkDone wait.
].
"Created: / 19-07-2019 / 11:19:27 / Claus Gittinger"
"Modified (format): / 19-07-2019 / 15:02:12 / Claus Gittinger"
!
wait:seconds
<action>
Delay waitForSeconds:seconds
"Created: / 19-07-2019 / 15:09:45 / Claus Gittinger"
! !
!ShowMeHowItWorks methodsFor:'commands - mouse'!
click:buttonNr
"press-release"
<action>
self click:buttonNr inComponent:lastComponent
"Created: / 19-07-2019 / 13:21:20 / Claus Gittinger"
"Modified: / 19-07-2019 / 14:55:18 / Claus Gittinger"
!
enter:aString
"enter text into the last component"
<action>
lastComponent simulateTextInput:aString at:(lastComponent extent // 2) sendDisplayEvent:false
"Created: / 19-07-2019 / 14:29:27 / Claus Gittinger"
!
moveTo:componentName
"move the mouse to componentName,
then circle around it a few times"
<action>
|component|
lastComponentName := componentName.
component := self findComponent:componentName.
component isNil ifTrue:[
self error:'no component found for: ',componentName.
].
lastComponent := component.
self movePointerToComponent:component.
self circlePointerAroundComponent:component.
"Created: / 19-07-2019 / 11:20:42 / Claus Gittinger"
"Modified (format): / 19-07-2019 / 14:55:27 / Claus Gittinger"
!
select:itemsLabel
"select an item by label,
allowed after moving to:
aComboBox
aSelectionInListView
"
<action>
|idx|
(lastComponent isKindOf:ComboView) ifTrue:[
"/ click on the menubutton
self movePointerToComponent:lastComponent menuButton.
self click:1 inComponent:lastComponent menuButton.
Delay waitForSeconds:0.3.
(idx := lastComponent list indexOf:itemsLabel ifAbsent:[nil]) isNil ifTrue:[
self error:'no such item in comboList: ',itemsLabel
].
lastComponent select:idx.
Delay waitForSeconds:0.3.
lastComponent shownMenu notNil ifTrue:[
lastComponent shownMenu hide.
].
^ self
].
self error:'cannot select this component'
"Created: / 19-07-2019 / 12:34:25 / Claus Gittinger"
"Modified (format): / 19-07-2019 / 14:55:34 / Claus Gittinger"
!
selectIndex:itemsIndex
"select an item by index,
allowed after moving to:
aComboBox
aSelectionInListView
"
<action>
(lastComponent isKindOf:ComboView) ifTrue:[
"/ click on the menubutton
self movePointerToComponent:lastComponent menuButton.
self click:1 inComponent:lastComponent menuButton.
Delay waitForSeconds:0.5.
lastComponent select:itemsIndex.
Delay waitForSeconds:0.5.
self halt.
^ self
].
self error:'cannot select this component'
"Created: / 19-07-2019 / 14:20:11 / Claus Gittinger"
! !
!ShowMeHowItWorks methodsFor:'defaults'!
circlingCount
"circle around move-end position that many times"
^ 3
"Created: / 19-07-2019 / 13:03:45 / Claus Gittinger"
!
circlingRadius
"radius when circling"
^ 30 "/ pixels
"Created: / 19-07-2019 / 13:07:59 / Claus Gittinger"
!
circlingSpeed
"time per round when circling"
^ 0.3 seconds. "/ time per round
"Created: / 19-07-2019 / 13:02:34 / Claus Gittinger"
!
clickTime
"when clicking"
^ 100 milliseconds
"Created: / 19-07-2019 / 13:17:20 / Claus Gittinger"
!
pointerAnimationDelay
^ 50 milliseconds. "/ 20 updates per second
"Created: / 19-07-2019 / 13:04:45 / Claus Gittinger"
!
pointerMoveSpeed
^ 400. "/ pixels per second
"Created: / 19-07-2019 / 13:05:40 / Claus Gittinger"
!
talking
^ true
"Created: / 19-07-2019 / 14:31:14 / Claus Gittinger"
! !
!ShowMeHowItWorks methodsFor:'helper'!
findComponent:componentName
"find a component by name - in the active and possibly in any app"
|app component candidates|
app := WindowGroup activeMainApplication.
app notNil ifTrue:[
component := self findComponent:componentName in:app.
].
component isNil ifTrue:[
"/ search through all current applications
candidates := OrderedCollection new.
WindowGroup scheduledWindowGroups do:[:eachWG |
|eachApp|
(eachApp := eachWG application) notNil ifTrue:[
component := self findComponent:componentName in:eachApp.
component notNil ifTrue:[
candidates add:component
].
].
].
candidates size == 1 ifTrue:[
component := candidates first
] ifFalse:[
candidates notEmpty ifTrue:[
self error:'multiple components found by name: ',componentName.
]
].
].
^ component
"Created: / 19-07-2019 / 12:02:30 / Claus Gittinger"
!
findComponent:componentName in:anApplication
|component componentNameSymbol foundByName foundByTitle foundByLabel|
(component := anApplication componentAt:componentName) notNil ifTrue:[^ component].
(componentNameSymbol := componentName asSymbolIfInterned) notNil ifTrue:[
(component := anApplication componentAt:componentNameSymbol) notNil ifTrue:[^ component].
].
"/ mhmh - search through all widgets of anApplication;
"/ maybe it was not created via the builder/spec,
"/ or it has changed its name.
"/ look for: widget's name, widget's title, widget's label
foundByName := OrderedCollection new.
foundByTitle := OrderedCollection new.
foundByLabel := OrderedCollection new.
anApplication window allSubViewsDo:[:each |
[
each name = componentName ifTrue:[ foundByName add:each ].
] on:MessageNotUnderstood do:[:ex | ].
[
each title = componentName ifTrue:[ foundByTitle add:each ].
] on:MessageNotUnderstood do:[:ex | ].
[
each label = componentName ifTrue:[ foundByLabel add:each ].
] on:MessageNotUnderstood do:[:ex | ].
].
foundByName notEmpty ifTrue:[
self assert:(foundByName size == 1) message:'multiple components found by name'.
^ foundByName first.
].
foundByTitle notEmpty ifTrue:[
self assert:(foundByTitle size == 1) message:'multiple components found by title'.
^ foundByTitle first.
].
foundByLabel notEmpty ifTrue:[
self assert:(foundByLabel size == 1) message:'multiple components found by label'.
^ foundByLabel first.
].
^ component
"Created: / 19-07-2019 / 11:36:21 / Claus Gittinger"
!
tell:message
self talking ifTrue:[
OperatingSystem speak:message.
].
"Created: / 19-07-2019 / 14:57:50 / Claus Gittinger"
! !
!ShowMeHowItWorks methodsFor:'helpers - broken'!
click:buttonNr atPosition:position
"press-release at position"
|screen|
screen := Screen current.
screen setPointerPosition:position.
screen flush.
self click:buttonNr
"Created: / 19-07-2019 / 13:14:51 / Claus Gittinger"
! !
!ShowMeHowItWorks methodsFor:'helpers - mouse movement'!
circlePointerAroundComponent:component
"circle around it a few times"
self circlePointerAroundPosition:(component screenBounds center rounded)
"Created: / 19-07-2019 / 13:12:35 / Claus Gittinger"
!
circlePointerAroundPosition:position
"circle around it a few times"
|screen stepDelayTime numCircles circlingSpeed radius|
screen := Screen current.
circlingSpeed := self circlingSpeed. "/ time per round
numCircles := self circlingCount.
stepDelayTime := self pointerAnimationDelay. "/ update interval
radius := self circlingRadius.
"/ move it around a few times
1 to:numCircles do:[:round |
|n angle|
n := circlingSpeed / stepDelayTime. "/ nr of steps per circle
angle := 360 / n. "/ angle-delta per step
1 to:n do:[:step |
|a x y|
a := angle * step.
"/ clockwise starting above the center
x := position x + (radius * a degreesToRadians sin).
y := position y + (radius * a degreesToRadians cos).
"/ Transcript showCR:(x@y).
screen setPointerPosition:(x@y) rounded.
screen flush.
Delay waitFor:stepDelayTime.
].
"/ and back
screen setPointerPosition:position rounded.
screen flush.
Delay waitFor:stepDelayTime.
].
"Created: / 19-07-2019 / 13:12:40 / Claus Gittinger"
!
click:buttonNr inComponent:component
"press-release in a component"
component simulateButtonPress:buttonNr at:(component extent // 2) sendDisplayEvent:false.
Delay waitForSeconds:(self clickTime).
component simulateButtonRelease:buttonNr at:(component extent // 2) sendDisplayEvent:false.
"/ self click:buttonNr atPosition:(component extent // 2)
"Created: / 19-07-2019 / 13:18:27 / Claus Gittinger"
!
movePointerToComponent:aWidget
"move the mouse to aWidget's center"
self movePointerToPosition:(aWidget screenBounds center rounded).
"Created: / 19-07-2019 / 13:11:33 / Claus Gittinger"
!
movePointerToPosition:newPosition
"move the mouse to newPosition"
|screen distance start numSteps moveTime stepDelayTime delta|
screen := Screen current.
start := screen pointerPosition.
distance := start dist:newPosition.
moveTime := (distance / self pointerMoveSpeed) seconds. "/ time to move
stepDelayTime := self pointerAnimationDelay. "/ update every 50ms
numSteps := moveTime / stepDelayTime.
numSteps = 0 ifTrue:[
"/ already there
^ self
].
delta := (newPosition - start) / numSteps.
1 to:numSteps do:[:step |
|p|
p := (start + (delta * step)) rounded.
"/ Transcript showCR:p.
screen setPointerPosition:p.
screen flush.
Delay waitFor:stepDelayTime.
].
"Created: / 19-07-2019 / 12:57:30 / Claus Gittinger"
!
press:buttonNr
"press at the current position"
|position screen x y|
screen := Screen current.
position := screen pointerPosition.
x := position x.
y := position y.
self movePointerToPosition:position.
false "OperatingSystem isOSXlike" ifTrue:[
|osxPos|
osxPos := OperatingSystem getMousePosition.
x := osxPos x rounded.
y := osxPos y rounded.
OperatingSystem generateButtonEvent:buttonNr down:true x:x y:y.
^ self.
].
screen sendKeyOrButtonEvent:#buttonPress x:x y:y keyOrButton:buttonNr state:0 toViewId:(screen rootWindowId).
screen flush.
"Created: / 19-07-2019 / 13:52:38 / Claus Gittinger"
!
release:buttonNr
"press-release at the current position"
|position screen x y|
screen := Screen current.
position := screen pointerPosition.
x := position x.
y := position y.
self movePointerToPosition:position.
false "OperatingSystem isOSXlike" ifTrue:[
|osxPos|
osxPos := OperatingSystem getMousePosition.
x := osxPos x rounded.
y := osxPos y rounded.
OperatingSystem generateButtonEvent:buttonNr down:false x:x y:y.
^ self.
].
screen sendKeyOrButtonEvent:#buttonRelease x:x y:y keyOrButton:buttonNr state:0 toViewId:(screen rootWindowId).
screen flush.
"Created: / 19-07-2019 / 13:53:05 / Claus Gittinger"
! !
!ShowMeHowItWorks methodsFor:'running'!
doStream:specStream
|previousStream|
previousStream := opStream.
[
opStream := specStream.
[opStream atEnd] whileFalse:[
self nextCommand.
Display shiftDown ifTrue:[
self tell:'Shou stopped by shiftkee'.
^ AbortOperationRequest raise
].
].
] ensure:[
opStream := previousStream
].
"<<END
ShowMeHowItWorks do:#(
showing: 'Choose the number of arguments'
do: (
moveTo: NumberOfArguments
select: '1'
)
showing: 'Click into the "receiver" field'
do: (
moveTo: ReceiverEditor
click: ReceiverEditor
)
showing: 'Enter a value (or expression) into "receiver" field'
do: (
enter: '100'
)
showing: 'Click into the "first argument" field'
do: (
moveTo: Arg1Editor
click: ReceiverEditor
)
showing: 'Enter a value (or expression) into "receiver" field'
do: (
enter: '100'
)
)
END"
"Created: / 19-07-2019 / 10:52:24 / Claus Gittinger"
"Modified: / 19-07-2019 / 15:00:44 / Claus Gittinger"
!
nextCommand
|op numArgs sel args method|
op := opStream next.
op isArray ifTrue:[
"/ construct a selector from keyword parts at odd indices
sel := ((op with:(1 to:op size) select:[:el :idx | idx odd]) asStringWith:'') asSymbol.
"/ construct arg vector from parts at even indices
args := op with:(1 to:op size) select:[:el :idx | idx even].
] ifFalse:[
sel := op.
numArgs := sel argumentCount.
args := opStream next:numArgs.
].
(self respondsTo:sel) ifFalse:[
self error:'bad operation: ',sel
].
method := self class lookupMethodFor:sel.
(method hasAnnotation:#action) ifFalse:[self halt].
self perform:sel withArguments:args.
"<<END
ShowMeHowItWorks do:#(
showing: 'Choose the number of arguments'
do: (
moveTo: NumberOfArguments
select: '1'
)
showing: 'Click into the "receiver" field'
do: (
moveTo: ReceiverEditor
click: ReceiverEditor
)
showing: 'Enter a value (or expression) into "receiver" field'
do: (
enter: '100'
)
showing: 'Click into the "first argument" field'
do: (
moveTo: Arg1Editor
click: ReceiverEditor
)
showing: 'Enter a value (or expression) into "receiver" field'
do: (
enter: '100'
)
)
END"
"Created: / 19-07-2019 / 10:54:04 / Claus Gittinger"
"Modified: / 19-07-2019 / 14:53:15 / Claus Gittinger"
! !
!ShowMeHowItWorks class methodsFor:'documentation'!
version_CVS
^ '$Header$'
! !