#FEATURE by exept
authorClaus Gittinger <cg@exept.de>
Sun, 20 Oct 2019 16:38:40 +0200
changeset 3798 eafc4c1bb53e
parent 3797 5ab4b911e388
child 3799 bbb4c1dbce25
#FEATURE by exept class: ShowMeHowItWorks added: #clear #fastMoveRelativeX:y: #freezePin:ofStep:with: #key: #selectPin:ofStep: changed: #click:inComponent:clickTime: #findComponent:in: #movePointerToScreenPosition:speed: #screenBoundsOfComponent: #select: #withViewAndPositionFor:do: class: ShowMeHowItWorks::ItemInView added: #isItemInView #isVisible
ShowMeHowItWorks.st
--- a/ShowMeHowItWorks.st	Sun Oct 20 13:44:05 2019 +0200
+++ b/ShowMeHowItWorks.st	Sun Oct 20 16:38:40 2019 +0200
@@ -230,6 +230,28 @@
 
 !ShowMeHowItWorks methodsFor:'commands'!
 
+freezePin:pinName ofStep:stepName with:freezeString
+    <action>
+
+    |diagramEditor pinPO|
+
+    (diagramEditor := lastComponent) isScrollWrapper ifTrue:[
+        diagramEditor := lastComponent scrolledView
+    ].
+    pinPO := diagramEditor 
+            detectPOForWhich:[:po |
+                po isPin 
+                and:[ po name = pinName
+                and:[ po owningStepPO name = stepName ]]
+            ].
+    diagramEditor selection:{ pinPO }.
+    diagramEditor doubleClickOn:pinPO.
+    self fastMoveTo:(lastComponentName,'/','EditField').
+    self click.
+    self type:freezeString.
+    self key:#Return.
+!
+
 intro
     <action>
 
@@ -312,6 +334,25 @@
     self halt.
 !
 
+selectPin:pinName ofStep:stepName
+    <action>
+
+    |diagramEditor pinPo|
+
+    (diagramEditor := lastComponent) isScrollWrapper ifTrue:[
+        diagramEditor := lastComponent scrolledView
+    ].
+    pinPo := diagramEditor 
+            detectPOForWhich:[:po |
+                po isPin 
+                and:[ po name = pinName
+                and:[ po owningStepPO name = stepName ]]
+            ].
+    self movePointerToComponent:diagramEditor offset:pinPo frame center.
+    diagramEditor selection:{ pinPo }. 
+
+!
+
 show:message
     "showing (and speak) some message."
 
@@ -488,6 +529,16 @@
 
 !ShowMeHowItWorks methodsFor:'commands - mouse & keyboard'!
 
+clear
+    "clear entry fields"
+
+    <action>
+
+    verifying ifTrue:[^ self].
+
+    lastComponent contents:nil
+!
+
 click
     "press-release"
     
@@ -651,6 +702,21 @@
     "Modified (format): / 19-07-2019 / 14:55:34 / Claus Gittinger"
 !
 
+fastMoveRelativeX:dX y:dY
+    "move the mouse to a new relative position"
+    
+    <action>
+
+    |screen newPos currentPos|
+
+    verifying ifTrue:[^ self].
+
+    screen := Screen current.
+    currentPos := screen pointerPosition.   
+    newPos := currentPos + (dX @ dY).
+    self fastMovePointerToScreenPosition:newPos
+!
+
 fastMoveTo:componentName
     "move the mouse to componentName without circling"
 
@@ -671,6 +737,22 @@
     "Modified (comment): / 23-07-2019 / 09:33:31 / Claus Gittinger"
 !
 
+key:aKeySymbol
+    "press/release a key"
+
+    <action>
+
+    |t|
+
+    verifying ifTrue:[^ self].
+
+    t := Display ctrlDown ifTrue:[0.05] ifFalse:[0.1].
+    lastComponent 
+        simulateKey:aKeySymbol at:(lastComponent extent // 2) sendDisplayEvent:false keyPressTime:t
+
+    "Created: / 19-07-2019 / 15:50:40 / Claus Gittinger"
+!
+
 moveTo:componentName
     "move the mouse to componentName,
      then circle around it a few times"
@@ -827,8 +909,38 @@
         "/ component selection:idx.
         ^ self
     ].
+    (component isKindOf:SelectionInListModelView) ifTrue:[
+        |idx yPos|
 
-    self error:'cannot select this component'
+        itemsIndexOrLabelOrPattern isInteger ifTrue:[
+            idx := itemsIndexOrLabelOrPattern
+        ] ifFalse:[
+            itemsIndexOrLabelOrPattern includesMatchCharacters ifTrue:[
+                idx := component indexOfElementForWhich:[:el | el notNil and:[itemsIndexOrLabelOrPattern match:el string]]. 
+            ] ifFalse:[
+                idx := component indexOfElementForWhich:[:el | el notNil and:[el string = itemsIndexOrLabelOrPattern]].
+            ].
+            idx == 0 ifTrue:[
+                self error:'no such item in hierarchicalList: ',itemsIndexOrLabelOrPattern
+            ].
+        ].
+        yPos := component yVisibleOfLine:idx.
+        self movePointerToComponent:component offset:(0 @ (yPos + 3)).
+        component simulateButtonPress:1 at:(0 @ (yPos + 3)) sendDisplayEvent:false.
+        Delay waitForSeconds:0.1.
+        component simulateButtonRelease:1 at:(0 @ (yPos + 3)) sendDisplayEvent:false.
+        "/ component selection:idx.
+        ^ self
+    ].
+
+    (component isKindOf:ItemInView) ifTrue:[
+        component isMenuItem ifTrue:[
+            self movePointerToComponent:component.
+            self halt.
+        ].
+    ].
+
+    self error:('cannot select this component: %1' bindWith:component className)
 
     "Created: / 19-07-2019 / 12:34:25 / Claus Gittinger"
     "Modified (format): / 19-07-2019 / 14:55:34 / Claus Gittinger"
@@ -1156,21 +1268,35 @@
      Uses the NameKey of the spec, and optionally the label or modelKey.
      Can return either a view or a menu item"
 
-    |idxString idx app window component componentNameSymbol 
+    |quoted idxString idx app window component componentNameSymbol 
      foundByName foundByHelpKey foundByTitle foundByLabel item
      checkIfAllMenuItemsDoTheSame|
 
-    (componentNameOrPath includes:$/) ifTrue:[
-        (idx := componentNameOrPath indexOf:$/) ~~ 0 ifTrue:[
-            |containerName restPath container|
+    "/ split at "/";
+    "/ but not if inside quotes
+    quoted := false.
+    componentNameOrPath doWithIndex:[:ch :idx |
+        ch == $" ifTrue:[
+            quoted := quoted not.
+        ] ifFalse:[
+            ((ch == $/) and:[quoted not]) ifTrue:[
+                |containerName restPath container|
 
-            containerName := componentNameOrPath copyTo:idx-1.
-            restPath := componentNameOrPath copyFrom:idx+1.
-            container := self findComponent:containerName in:anApplicationOrViewOrMenuItem.
-            container isNil ifTrue:[ ^ nil ].
-            ^ self findComponent:restPath in:container
+                containerName := componentNameOrPath copyTo:idx-1.
+                restPath := componentNameOrPath copyFrom:idx+1.
+                container := self findComponent:containerName in:anApplicationOrViewOrMenuItem.
+                container isNil ifTrue:[ ^ nil ].
+                ^ self findComponent:restPath in:container
+            ]
         ]
     ].
+
+    (anApplicationOrViewOrMenuItem isKindOf:ItemInView) ifTrue:[
+        anApplicationOrViewOrMenuItem isMenuItem ifTrue:[
+            ^ self findComponent:componentNameOrPath in:anApplicationOrViewOrMenuItem item submenu
+        ].
+    ].
+
     (componentNameOrPath startsWith:'item[') ifTrue:[
         anApplicationOrViewOrMenuItem isMenu ifFalse:[
             self assert:false message:'container is not a menu'.
@@ -1180,9 +1306,24 @@
             idx := Integer readFrom:idxString.
             item := anApplicationOrViewOrMenuItem itemAtIndex:idx.
         ] ifFalse:[
-            item := anApplicationOrViewOrMenuItem itemAt:idxString.
+            idxString := idxString withoutQuotes.
+            idxString includesMatchCharacters ifTrue:[
+                item := 
+                    anApplicationOrViewOrMenuItem itemForWhich:[:item |
+                        (item nameKey matches: idxString)
+                        or:[ (item textLabel matches: idxString) 
+                        or:[ (item value isSymbol and:[item value matches: idxString])]]
+                    ]
+            ] ifFalse:[
+                item := 
+                    anApplicationOrViewOrMenuItem itemForWhich:[:item |
+                        item nameKey = idxString
+                        or:[ item textLabel = idxString 
+                        or:[ (item value isSymbol and:[ item value = idxString ])]]
+                    ]
+            ].
         ].
-        self assert:item notNil.
+        self assert:item notNil message:('no menu item named "%1"' bindWith:idxString).
         ^ ItemInView new view:anApplicationOrViewOrMenuItem item:item boundsInView:(item layout)
     ].
     (componentNameOrPath startsWith:'tab[') ifTrue:[
@@ -1194,6 +1335,7 @@
             idx := Integer readFrom:idxString.
             item := anApplicationOrViewOrMenuItem tabAtIndex:idx.
         ] ifFalse:[
+            idxString := idxString withoutQuotes.
             item := anApplicationOrViewOrMenuItem tabForWhich:[:tab | tab tabItem rawLabel = idxString].
             item isNil ifTrue:[
                 item := anApplicationOrViewOrMenuItem tabForWhich:[:tab | tab printableLabel = idxString].
@@ -1398,7 +1540,7 @@
 
         aWidgetOrMenuItemOrTab isVisible ifTrue:[
             (itemLayout := aWidgetOrMenuItemOrTab layout) notNil ifTrue:[
-                menuPanel := aWidgetOrMenuItemOrTab menuPanel.
+                menuPanel := aWidgetOrMenuItemOrTab view.
                 menuPanel shown ifTrue:[
                     menuBounds := menuPanel screenBounds.
                     ^ itemLayout + menuBounds origin 
@@ -1444,9 +1586,27 @@
     "press-release in a component"
 
     self withViewAndPositionFor:viewOrMenuItem do:[:viewToClick :clickPos |
-        viewToClick simulateButtonPress:buttonNr at:clickPos sendDisplayEvent:false.
-        Delay waitForSeconds:clickTime.
-        viewToClick simulateButtonRelease:buttonNr at:clickPos sendDisplayEvent:false.
+        Display activePointerGrab == Display rootView ifTrue:[
+            |pos|
+
+            pos := Display translatePoint:clickPos fromView:viewToClick toView:nil.
+            OperatingSystem isOSXlike ifTrue:[
+                OperatingSystem 
+                    generateMouseMoveEventX:pos x y:pos y;
+                    generateButtonEvent:buttonNr down:true x:pos x y:pos y;
+                    generateButtonEvent:buttonNr down:false x:pos x y:pos y;
+                    generateButtonEvent:buttonNr down:true x:pos x y:pos y;
+                    generateButtonEvent:buttonNr down:false x:pos x y:pos y.
+                ^ self.
+            ].
+            Display rootView simulateButtonPress:buttonNr at:pos sendDisplayEvent:true.
+            Delay waitForSeconds:clickTime.
+            Display rootView simulateButtonRelease:buttonNr at:pos sendDisplayEvent:true.
+        ] ifFalse:[
+            viewToClick simulateButtonPress:buttonNr at:clickPos sendDisplayEvent:false.
+            Delay waitForSeconds:clickTime.
+            viewToClick simulateButtonRelease:buttonNr at:clickPos sendDisplayEvent:false.
+        ].
     ].
 !
 
@@ -1474,8 +1634,7 @@
     self assert:viewOrMenuItem notNil.
     verifying ifTrue:[^ self].
 
-    ((viewOrMenuItem askFor:#isMenuItem) 
-    or:[ (viewOrMenuItem askFor:#isNoteBookTab) ]) ifTrue:[
+    (viewOrMenuItem isKindOf:ItemInView) ifTrue:[
         viewToClick := viewOrMenuItem view.
         clickPos := viewOrMenuItem layout center.
         self assert:(clickPos notNil).
@@ -1638,12 +1797,19 @@
     start := screen pointerPosition.   
 
     distance := start dist:newPosition.
+    distance = 0 ifTrue:[
+        "/ already there
+        screen setPointerPosition:newPosition. "/ do it anyway, in case of rounding errors
+        ^ self
+    ].
+
     moveTime := (distance / pixelsPerSecond) seconds.   "/ time to move
     stepDelayTime := self pointerAnimationDelay.        "/ update every 50ms
     
     numSteps := moveTime / stepDelayTime.
     numSteps = 0 ifTrue:[
         "/ already there
+        screen setPointerPosition:newPosition. "/ do it anyway, in case of rounding errors
         ^ self
     ].
     
@@ -2111,12 +2277,21 @@
 
 !ShowMeHowItWorks::ItemInView methodsFor:'testing'!
 
+isItemInView
+    ^ true
+!
+
 isMenuItem
     ^ item askFor:#isMenuItem
 !
 
 isNoteBookTab
     ^ item isKindOf:NoteBookView::Tab
+!
+
+isVisible
+    ^ view shown
+    and:[item isVisible]
 ! !
 
 !ShowMeHowItWorks class methodsFor:'documentation'!