#FEATURE by exept
class: ShowMeHowItWorks
class definition
added:
#open:
#screenBoundsOfComponent:
#waitFor:timeout:
comment/format in: #talking
changed:
#circlePointerAroundComponent:
#click:inComponent:clickTime:
#do:
#findComponent:
#findComponent:in:
#movePointerToComponent:
#movePointerToComponent:offset:
#movePointerToComponent:speed:
#show:for:
#show:saying:for:
#showing:saying:do:
#wait:
category of:
#click:inComponent:
#click:inComponent:clickTime:
#press:
#release:
--- a/ShowMeHowItWorks.st Fri Oct 11 10:31:27 2019 +0200
+++ b/ShowMeHowItWorks.st Sun Oct 13 23:27:40 2019 +0200
@@ -6,8 +6,8 @@
Object subclass:#ShowMeHowItWorks
instanceVariableNames:'application opStream lastComponentName lastComponent lastResult
- voice translate language verifying'
- classVariableNames:'IntroShownCount'
+ voice translate language verifying closeApplicationWhenFinished'
+ classVariableNames:'IntroShownCount DebugMode'
poolDictionaries:''
category:'Interface-Help'
!
@@ -133,6 +133,22 @@
"Created: / 23-07-2019 / 10:27:02 / Claus Gittinger"
!
+open:applicationClassName
+ <action>
+
+ |appClass|
+
+ (appClass := Smalltalk classNamed:applicationClassName) isNil ifTrue:[
+ self error:'no such application class'
+ ].
+ verifying ifFalse:[
+ application := appClass new openAndWaitUntilVisible.
+ closeApplicationWhenFinished := true.
+ ].
+
+ "Created: / 19-07-2019 / 15:09:45 / Claus Gittinger"
+!
+
pause
<action>
@@ -178,7 +194,9 @@
"showing (and speak) some message and wait for some time."
<action>
-
+
+ DebugMode == true ifTrue:[^ self].
+
self show:message.
self wait:seconds.
@@ -201,6 +219,7 @@
<action>
+ DebugMode == true ifTrue:[^ self].
self show:message saying:sentenceOrNil.
self wait:seconds.
@@ -231,7 +250,8 @@
messageView := ActiveHelpView for:xLatedMessage.
"/ messageView shapeStyle:#cartoon.
[
- messageView origin:(Screen current pointerPosition).
+ messageView origin:(Screen current pointerPosition + (0 @ 20)).
+ messageView makeFullyVisible.
messageView realize.
self talking ifTrue:[
@@ -274,11 +294,31 @@
<action>
verifying ifTrue:[^ self].
+ DebugMode == true ifTrue:[^ self].
Display ctrlDown ifTrue:[^ self].
Delay waitForSeconds:seconds
"Created: / 19-07-2019 / 15:09:45 / Claus Gittinger"
+!
+
+waitFor:componentName timeout:seconds
+ <action>
+
+ |endTime|
+
+ DebugMode == true ifTrue:[^ self].
+ endTime := Timestamp now + seconds.
+ [
+ (self findComponent:componentName) notNil ifTrue:[
+ self halt.
+ ^ self
+ ].
+ Delay waitForSeconds:0.05.
+ Timestamp now > endTime ifTrue:[
+ self error:('component %1 not present after %2' bindWith:componentName with:seconds)
+ ]
+ ] loop
! !
!ShowMeHowItWorks methodsFor:'commands - checking'!
@@ -596,7 +636,9 @@
!
talking
+ "/ DebugMode := true
verifying ifTrue:[^ false].
+ DebugMode == true ifTrue:[^ false].
"/ ^ Expecco::ExpeccoPreferences current speechEffectsEnabled
^ true
@@ -699,24 +741,25 @@
|component candidates modalGroup|
application notNil ifTrue:[
- component := self findComponent:componentName in:application.
+ (component := self findComponent:componentName in:application) notNil ifTrue:[
+ ^ component.
+ ].
].
- component isNil ifTrue:[
- candidates := OrderedCollection new.
+ candidates := OrderedCollection new.
- "/ is there a modal dialog open for the app?
- (modalGroup := application windowGroup modalGroup) notNil ifTrue:[
- modalGroup topViews do:[:eachModalTopView |
- component := self findComponent:componentName in:eachModalTopView.
- component notNil ifTrue:[
- candidates add:component
- ].
- ].
- candidates size == 1 ifTrue:[
- ^ candidates first
+ "/ is there a modal dialog open for the app?
+ (modalGroup := application windowGroup modalGroup) notNil ifTrue:[
+ modalGroup topViews do:[:eachModalTopView |
+ component := self findComponent:componentName in:eachModalTopView.
+ component notNil ifTrue:[
+ candidates add:component
].
].
-
+ candidates size == 1 ifTrue:[
+ ^ candidates first
+ ].
+ ].
+ application isNil ifTrue:[
"/ search through all current applications
WindowGroup scheduledWindowGroups do:[:eachWG |
|eachApp|
@@ -728,18 +771,18 @@
].
].
].
-
+
candidates size == 1 ifTrue:[
- component := candidates first
- ] ifFalse:[
- candidates notEmpty ifTrue:[
- "/ multiple elements (probably there are multiple topviews open...
- "/ check the current windowGroup
- self error:'multiple components found by name: ',componentName.
- ]
- ].
- ].
- ^ component
+ ^ candidates first
+ ].
+ candidates notEmpty ifTrue:[
+ "/ multiple elements (probably there are multiple topviews open...
+ "/ check the current windowGroup
+ self error:'multiple components found by name: ',componentName.
+ ].
+ ].
+
+ ^ nil
"
ShowMeHowItWorks basicNew findComponent:'Classes'
@@ -751,23 +794,35 @@
"Modified (comment): / 23-07-2019 / 09:32:44 / Claus Gittinger"
!
-findComponent:componentName in:anApplicationOrView
- "find a component by name inside an app.
+findComponent:componentNameOrPath in:anApplicationOrView
+ "find a component by name inside an app or inside a view.
Uses the NameKey of the spec, and optionally the label or modelKey.
Can return either a view or a menu item"
- |app window component componentNameSymbol foundByName foundByTitle foundByLabel item|
+ |idx app window component componentNameSymbol foundByName foundByTitle foundByLabel item
+ checkIfAllMenuItemsDoTheSame|
+
+ (componentNameOrPath includes:$/) ifTrue:[
+ (idx := componentNameOrPath indexOf:$/) ~~ 0 ifTrue:[
+ |containerName restPath container|
+
+ containerName := componentNameOrPath copyTo:idx-1.
+ restPath := componentNameOrPath copyFrom:idx+1.
+ container := self findComponent:containerName in:anApplicationOrView.
+ container isNil ifTrue:[ ^ nil ].
+ ^ self findComponent:restPath in:container
+ ]
+ ].
+
+ componentNameSymbol := componentNameOrPath asSymbolIfInterned ? componentNameOrPath.
(app := anApplicationOrView) isView ifTrue:[
window := anApplicationOrView.
app := anApplicationOrView application
].
app notNil ifTrue:[
- (component := app componentAt:componentName) notNil ifTrue:[^ component].
- (componentNameSymbol := componentName asSymbolIfInterned) notNil ifTrue:[
- (component := app componentAt:componentNameSymbol) notNil ifTrue:[^ component].
- ].
- window := app window.
+ (component := app componentAt:componentNameSymbol) notNil ifTrue:[^ component].
+ window := window ? app window.
].
"/ mhmh - search through all widgets of anApplication;
@@ -778,51 +833,76 @@
foundByTitle := OrderedCollection new.
foundByLabel := OrderedCollection new.
- window allSubViewsDo:[:each |
+ window withAllSubViewsDo:[:each |
|foundIt|
foundIt := false.
- [
- each name = componentName ifTrue:[ foundByName add:each. foundIt := true ].
- ] on:MessageNotUnderstood do:[:ex | ].
- foundIt ifFalse:[
+ each shown ifTrue:[
[
- each title = componentName ifTrue:[ foundByTitle add:each. foundIt := true ].
+ each name = componentNameSymbol ifTrue:[ foundByName add:each. foundIt := true ].
] on:MessageNotUnderstood do:[:ex | ].
foundIt ifFalse:[
[
- each label = componentName ifTrue:[ foundByLabel add:each. foundIt := true ].
+ each title = componentNameSymbol ifTrue:[ foundByTitle add:each. foundIt := true ].
] on:MessageNotUnderstood do:[:ex | ].
foundIt ifFalse:[
- each isMenu ifTrue:[
- (item := each detectItemForNameKey:componentName) notNil ifTrue:[
- foundByName add:item. foundIt := true
- ].
- foundIt ifFalse:[
- (item := each detectItemForKey:componentName) notNil ifTrue:[
- foundByName add:item. foundIt := true
- ].
+ [
+ each label = componentNameSymbol ifTrue:[ foundByLabel add:each. foundIt := true ].
+ ] on:MessageNotUnderstood do:[:ex | ].
+ foundIt ifFalse:[
+ each isMenu ifTrue:[
+ verifying ifFalse:[
+ componentNameSymbol = 'NewTestSuite' ifTrue:[self halt].
+ ].
+ (item := each detectItemForNameKey:componentNameSymbol) notNil ifTrue:[
+ foundByName add:item. foundIt := true
+ ].
foundIt ifFalse:[
- (item := each detectItemForLabel:componentName) notNil ifTrue:[
- foundByLabel add:item. foundIt := true
+ (item := each detectItemForKey:componentNameSymbol) notNil ifTrue:[
+ foundByName add:item. foundIt := true
].
+ foundIt ifFalse:[
+ (item := each detectItemForLabel:componentNameSymbol) notNil ifTrue:[
+ foundByLabel add:item. foundIt := true
+ ].
+ ].
].
- ].
- ].
- ].
- ]
+ ].
+ ].
+ ]
+ ].
].
].
-
+
+ "/ a check, if multiple menu items have the same action, then choose the first found
+ checkIfAllMenuItemsDoTheSame :=
+ [:itemsFound |
+ |visibleItems|
+
+ (itemsFound conform:[:each | each askFor:#isMenuItem]) ifTrue:[
+ (itemsFound collect:[:item | item itemValue]) asSet size == 1 ifTrue:[
+ "/ choose one which is visible, if possible
+ visibleItems := itemsFound select:[:item | item menuPanel shown].
+ visibleItems notEmpty ifTrue:[
+ ^ visibleItems first
+ ].
+ ^ itemsFound first.
+ ].
+ ].
+ ].
+
foundByName notEmpty ifTrue:[
+ checkIfAllMenuItemsDoTheSame value:foundByName.
self assert:(foundByName size == 1) message:'multiple components found by name'.
^ foundByName first.
].
foundByTitle notEmpty ifTrue:[
+ checkIfAllMenuItemsDoTheSame value:foundByTitle.
self assert:(foundByTitle size == 1) message:'multiple components found by title'.
^ foundByTitle first.
].
foundByLabel notEmpty ifTrue:[
+ checkIfAllMenuItemsDoTheSame value:foundByLabel.
self assert:(foundByLabel size == 1) message:'multiple components found by label'.
^ foundByLabel first.
].
@@ -834,151 +914,29 @@
"Created: / 19-07-2019 / 11:36:21 / Claus Gittinger"
"Modified (comment): / 23-07-2019 / 09:31:34 / Claus Gittinger"
-! !
-
-!ShowMeHowItWorks methodsFor:'helpers - mouse movement'!
-
-circlePointerAroundComponent:aWidgetOrMenuItem
- "circle around it a few times"
-
- |position|
-
- aWidgetOrMenuItem isView ifTrue:[
- position := aWidgetOrMenuItem screenBounds center rounded
- ] ifFalse:[
- self error:'not a widget: ',aWidgetOrMenuItem printString.
- ].
- self circlePointerAroundScreenPosition:position
-
- "Created: / 19-07-2019 / 13:12:35 / Claus Gittinger"
- "Modified: / 23-07-2019 / 09:38:12 / Claus Gittinger"
-!
-
-circlePointerAroundScreenPosition:position
- "circle around it a few times"
-
- |screen stepDelayTime numCircles circlingSpeed radius|
-
- verifying ifTrue:[^ self].
-
- 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: / 23-07-2019 / 09:37:46 / Claus Gittinger"
-!
-
-fastMovePointerToScreenPosition:position
- self movePointerToScreenPosition:position speed:(self pointerMoveSpeedFast).
-
- "Created: / 23-07-2019 / 09:36:20 / Claus Gittinger"
!
-movePointerToComponent:aWidget
- "move the mouse to aWidget's center"
-
- self movePointerToScreenPosition:(aWidget screenBounds center rounded).
-
- "Created: / 19-07-2019 / 13:11:33 / Claus Gittinger"
- "Modified: / 23-07-2019 / 09:37:01 / Claus Gittinger"
-!
-
-movePointerToComponent:aWidget offset:offset
- "move the mouse to position inside aWidget's"
-
- self movePointerToScreenPosition:(aWidget screenBounds origin + offset) rounded.
-
- "Created: / 19-07-2019 / 16:18:58 / Claus Gittinger"
- "Modified: / 23-07-2019 / 09:36:57 / Claus Gittinger"
-!
-
-movePointerToComponent:aWidgetOrMenuItem speed:pixelsPerSecond
- "move the mouse to aWidget's center"
-
- |position|
-
+screenBoundsOfComponent:aWidgetOrMenuItem
aWidgetOrMenuItem isView ifTrue:[
- position := aWidgetOrMenuItem screenBounds center rounded
- ] ifFalse:[
- self error:'not a widget: ',aWidgetOrMenuItem printString.
- ].
- self movePointerToScreenPosition:position speed:pixelsPerSecond.
-
- "Created: / 20-07-2019 / 08:12:49 / Claus Gittinger"
- "Modified: / 23-07-2019 / 09:37:27 / Claus Gittinger"
-!
-
-movePointerToScreenPosition:newPosition
- "move the mouse to newPosition"
+ ^ aWidgetOrMenuItem screenBounds
+ ].
+ (aWidgetOrMenuItem askFor:#isMenuItem) ifTrue:[
+ |menuPanel menuBounds|
- self movePointerToScreenPosition:newPosition speed:(self pointerMoveSpeed)
-
- "Created: / 23-07-2019 / 09:36:39 / Claus Gittinger"
-!
-
-movePointerToScreenPosition:newPosition speed:pixelsPerSecond
- "move the mouse to newPosition, which is a screen position"
-
- |screen distance start numSteps moveTime stepDelayTime delta|
-
- verifying ifTrue:[^ self].
-
- screen := Screen current.
- start := screen pointerPosition.
-
- distance := start dist:newPosition.
- moveTime := (distance / pixelsPerSecond) seconds. "/ time to move
- stepDelayTime := self pointerAnimationDelay. "/ update every 50ms
-
- numSteps := moveTime / stepDelayTime.
- numSteps = 0 ifTrue:[
- "/ already there
- ^ self
+ aWidgetOrMenuItem isVisible ifTrue:[
+ aWidgetOrMenuItem layout notNil ifTrue:[
+ menuPanel := aWidgetOrMenuItem menuPanel.
+ menuPanel shown ifTrue:[
+ menuBounds := menuPanel screenBounds.
+ ^ aWidgetOrMenuItem layout + menuBounds origin
+ ].
+ ].
+ ].
].
-
- 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: / 23-07-2019 / 09:36:45 / Claus Gittinger"
+ ^ nil.
! !
-!ShowMeHowItWorks methodsFor:'menu actions - mouse buttons'!
+!ShowMeHowItWorks methodsFor:'helpers - mouse buttons'!
click:buttonNr inComponent:component
"press-release in a component"
@@ -995,15 +953,26 @@
"Modified: / 19-07-2019 / 15:22:47 / Claus Gittinger"
!
-click:buttonNr inComponent:component clickTime:clickTime
+click:buttonNr inComponent:viewOrMenuItem clickTime:clickTime
"press-release in a component"
- self assert:component notNil.
+ |viewToClick clickPos|
+
+ self assert:viewOrMenuItem notNil.
verifying ifTrue:[^ self].
- component simulateButtonPress:buttonNr at:(component extent // 2) sendDisplayEvent:false.
+ (viewOrMenuItem askFor:#isMenuItem) ifTrue:[
+ viewToClick := viewOrMenuItem menuPanel.
+ clickPos := viewOrMenuItem layout center.
+ self assert:(clickPos notNil).
+ ] ifFalse:[
+ viewToClick := viewOrMenuItem.
+ clickPos := viewToClick extent // 2.
+ ].
+
+ viewToClick simulateButtonPress:buttonNr at:clickPos sendDisplayEvent:false.
Delay waitForSeconds:clickTime.
- component simulateButtonRelease:buttonNr at:(component extent // 2) sendDisplayEvent:false.
+ viewToClick simulateButtonRelease:buttonNr at:clickPos sendDisplayEvent:false.
"/ self click:buttonNr atPosition:(component extent // 2)
@@ -1072,6 +1041,154 @@
"Modified: / 23-07-2019 / 09:38:38 / Claus Gittinger"
! !
+!ShowMeHowItWorks methodsFor:'helpers - mouse movement'!
+
+circlePointerAroundComponent:aWidgetOrMenuItem
+ "circle around it a few times"
+
+ |bounds position|
+
+ bounds := self screenBoundsOfComponent:aWidgetOrMenuItem.
+ bounds isNil ifTrue:[
+ self error:'no bounds found for: ',aWidgetOrMenuItem printString.
+ ].
+ position := bounds center rounded.
+ self circlePointerAroundScreenPosition:position
+
+ "Created: / 19-07-2019 / 13:12:35 / Claus Gittinger"
+ "Modified: / 23-07-2019 / 09:38:12 / Claus Gittinger"
+!
+
+circlePointerAroundScreenPosition:position
+ "circle around it a few times"
+
+ |screen stepDelayTime numCircles circlingSpeed radius|
+
+ verifying ifTrue:[^ self].
+
+ 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: / 23-07-2019 / 09:37:46 / Claus Gittinger"
+!
+
+fastMovePointerToScreenPosition:position
+ self movePointerToScreenPosition:position speed:(self pointerMoveSpeedFast).
+
+ "Created: / 23-07-2019 / 09:36:20 / Claus Gittinger"
+!
+
+movePointerToComponent:aWidgetOrMenuItem
+ "move the mouse to aWidget's center"
+
+ |bounds|
+
+ bounds := self screenBoundsOfComponent:aWidgetOrMenuItem.
+ self movePointerToScreenPosition:(bounds center rounded).
+
+ "Created: / 19-07-2019 / 13:11:33 / Claus Gittinger"
+ "Modified: / 23-07-2019 / 09:37:01 / Claus Gittinger"
+!
+
+movePointerToComponent:aWidgetOrMenuItem offset:offset
+ "move the mouse to position inside aWidget's"
+
+ |bounds|
+
+ bounds := self screenBoundsOfComponent:aWidgetOrMenuItem.
+ self movePointerToScreenPosition:(bounds origin + offset) rounded.
+
+ "Created: / 19-07-2019 / 16:18:58 / Claus Gittinger"
+ "Modified: / 23-07-2019 / 09:36:57 / Claus Gittinger"
+!
+
+movePointerToComponent:aWidgetOrMenuItem speed:pixelsPerSecond
+ "move the mouse to aWidget's center"
+
+ |bounds position|
+
+ bounds := self screenBoundsOfComponent:aWidgetOrMenuItem.
+ bounds isNil ifTrue:[
+ self error:'no bounds found for: ',aWidgetOrMenuItem printString.
+ ].
+ position := bounds center rounded.
+ self movePointerToScreenPosition:position speed:pixelsPerSecond.
+
+ "Created: / 20-07-2019 / 08:12:49 / Claus Gittinger"
+ "Modified: / 23-07-2019 / 09:37:27 / Claus Gittinger"
+!
+
+movePointerToScreenPosition:newPosition
+ "move the mouse to newPosition"
+
+ self movePointerToScreenPosition:newPosition speed:(self pointerMoveSpeed)
+
+ "Created: / 23-07-2019 / 09:36:39 / Claus Gittinger"
+!
+
+movePointerToScreenPosition:newPosition speed:pixelsPerSecond
+ "move the mouse to newPosition, which is a screen position"
+
+ |screen distance start numSteps moveTime stepDelayTime delta|
+
+ verifying ifTrue:[^ self].
+
+ screen := Screen current.
+ start := screen pointerPosition.
+
+ distance := start dist:newPosition.
+ moveTime := (distance / pixelsPerSecond) 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: / 23-07-2019 / 09:36:45 / Claus Gittinger"
+! !
+
!ShowMeHowItWorks methodsFor:'running'!
do:specArray
@@ -1093,6 +1210,7 @@
application isNil ifTrue:[
application := WindowGroup activeMainApplication.
].
+ closeApplicationWhenFinished := false.
"/ run in verifying mode
verifying := true.