# HG changeset patch # User Claus Gittinger # Date 1571002060 -7200 # Node ID 604d8ffc8903cf087f8eead586b057963b710e6a # Parent f896a3f5e52345a8391faf3168a5965fd7c15b83 #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: diff -r f896a3f5e523 -r 604d8ffc8903 ShowMeHowItWorks.st --- 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 + + + |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 @@ -178,7 +194,9 @@ "showing (and speak) some message and wait for some time." - + + DebugMode == true ifTrue:[^ self]. + self show:message. self wait:seconds. @@ -201,6 +219,7 @@ + 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 @@ 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 + + + |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.