#FEATURE by exept draft
authorClaus Gittinger <cg@exept.de>
Mon, 14 Oct 2019 20:55:04 +0200
changeset 3787 7b01435dcf25
parent 3786 30b4d8d63f81
child 3788 f15afc1c9b20
#FEATURE by exept class: ShowMeHowItWorks added: #do:from: #do:from:withUI: #doStream:from: #drag:toComponent:dropAt: #expand: #findComponent:ifMultiple: #findComponent:inAllApplications:ifMultiple: #label: #movePointerToComponent:rightBottomOffset: #movePointerToComponent:rightOffset: removed: #nextCommand comment/format in: #do: #findComponent: #waitFor:timeout: changed: #do:withUI: #doCommand: #doStream: #press: #release: #select: #selectIndex:in: class: ShowMeHowItWorks class added: #do:from:withUI:
ShowMeHowItWorks.st
--- a/ShowMeHowItWorks.st	Mon Oct 14 14:49:21 2019 +0200
+++ b/ShowMeHowItWorks.st	Mon Oct 14 20:55:04 2019 +0200
@@ -95,6 +95,29 @@
     "Modified (comment): / 23-07-2019 / 10:26:42 / Claus Gittinger"
 !
 
+do:specArray from:startLabelOrNil withUI:withUIBoolean
+    "spec contains a list of action commands (show: / moveTo: etc.)"
+
+    self new 
+        do:specArray 
+        from:startLabelOrNil
+        withUI:withUIBoolean
+
+    "
+     ShowMeHowItWorks 
+        do:#(
+            (language: de)
+            (show: 'üben üben üben')
+            (wait: 0.5)
+            (moveTo: NameOfComponent)
+        )
+        withUI:true
+    "
+
+    "Created: / 19-07-2019 / 10:52:59 / Claus Gittinger"
+    "Modified (comment): / 23-07-2019 / 10:26:42 / Claus Gittinger"
+!
+
 do:specArray withUI:withUIBoolean
     "spec contains a list of action commands (show: / moveTo: etc.)"
 
@@ -144,6 +167,12 @@
     "Modified: / 23-07-2019 / 11:06:13 / Claus Gittinger"
 !
 
+label:nameOfLabel
+    <action>
+
+    "/ skipped here; see goto
+!
+
 language:lang
     <action>
 
@@ -331,8 +360,8 @@
     verifying ifTrue:[^ self].
 
     endTime := Timestamp now + seconds.
-    [
-        (self findComponent:componentName) notNil ifTrue:[
+    [   
+        (self findComponent:componentName inAllApplications:true ifMultiple:nil) notNil ifTrue:[
             ^ self
         ].
         Delay waitForSeconds:0.05.
@@ -408,6 +437,134 @@
     "Created: / 19-07-2019 / 16:09:58 / Claus Gittinger"
 !
 
+drag:itemsIndexOrLabelOrPattern toComponent:targetComponentName dropAt:where
+    "drag an item (by index or label) from the current treeView,
+     into another component.
+     where is one of: #top, #center or #bottom.
+     allowed after moving to:
+        aSelectionInHierarchyView
+    "    
+
+    <action>
+
+    |sourceComponent targetComponent idx yPos sourcePos sourcePosOnScreen
+     targetPos targetPosOnScreen targetPosInSource|
+
+    self assert:( #(top center bottom) includes:where).
+
+    verifying ifTrue:[^ self].
+
+    targetComponent := self findComponent:targetComponentName.
+    self assert:targetComponent notNil.
+
+    ((sourceComponent := lastComponent) isKindOf:ScrollableView) ifTrue:[
+        sourceComponent := sourceComponent scrolledView.
+    ].
+
+    (sourceComponent isKindOf:HierarchicalListView) ifTrue:[
+        where == #top ifTrue:[
+            targetPos := targetComponent topCenter. 
+        ] ifFalse:[
+            where == #center ifTrue:[
+                targetPos := targetComponent center
+            ] ifFalse:[
+                where == #bottom ifTrue:[
+                    targetPos := targetComponent bottomCenter - (0 @ 1)
+                ].
+            ].
+        ].
+
+        itemsIndexOrLabelOrPattern isInteger ifTrue:[
+            idx := itemsIndexOrLabelOrPattern
+        ] ifFalse:[
+            itemsIndexOrLabelOrPattern includesMatchCharacters ifTrue:[
+                idx := sourceComponent indexOfElementForWhich:[:el | itemsIndexOrLabelOrPattern match:el label string].
+            ] ifFalse:[
+                idx := sourceComponent indexOfElementForWhich:[:el | el label string = itemsIndexOrLabelOrPattern].
+            ].
+            idx == 0 ifTrue:[
+                self error:'no such item in hierarchicalList: ',itemsIndexOrLabelOrPattern
+            ].
+        ].
+        yPos := sourceComponent yVisibleOfLine:idx.
+        self movePointerToComponent:sourceComponent rightOffset:(10 @ (yPos + 3)).
+        Delay waitForSeconds:0.5.
+        sourcePosOnScreen := Display pointerPosition.
+        sourcePos := Display translatePoint:sourcePosOnScreen from:nil to:sourceComponent.
+
+        targetPosOnScreen := Display translatePoint:targetPos from:targetComponent to:nil.
+        targetPosInSource := Display translatePoint:targetPosOnScreen from:nil to:sourceComponent.
+
+        sourceComponent simulateButtonPress:1 at:sourcePos sendDisplayEvent:true.
+        Delay waitForSeconds:0.5.
+        self movePointerToComponent:targetComponent offset:targetPos.
+        sourceComponent simulateButtonMotion:1 to:targetPosInSource sendDisplayEvent:false.
+        sourceComponent simulateButtonRelease:1 at:targetPosInSource sendDisplayEvent:false.
+self halt.
+        sourceComponent simulateButtonMotion:1 to:targetPosInSource sendDisplayEvent:false.
+        Delay waitForSeconds:0.5.
+        sourceComponent simulateButtonRelease:1 at:targetPosInSource sendDisplayEvent:true.
+
+"/        self movePointerToComponent:targetComponent offset:targetPos.
+"/        Delay waitForSeconds:2.
+"/        self press:1.
+"/        Delay waitForSeconds:2.
+"/        self release:1.
+"/        Delay waitForSeconds:0.5.
+        self halt.
+        ^ self
+    ].
+
+    self error:'cannot expand in this component'
+
+    "Created: / 19-07-2019 / 12:34:25 / Claus Gittinger"
+    "Modified (format): / 19-07-2019 / 14:55:34 / Claus Gittinger"
+!
+
+expand:itemsIndexOrLabelOrPattern
+    "expand an item in a treeView by label,
+     allowed after moving to:
+        aSelectionInHierarchyView
+    "    
+
+    <action>
+
+    |component|
+
+    verifying ifTrue:[^ self].
+
+    ((component := lastComponent) isKindOf:ScrollableView) ifTrue:[
+        component := component scrolledView.
+    ].
+
+    (component isKindOf:HierarchicalListView) ifTrue:[
+        |idx yPos|
+
+        itemsIndexOrLabelOrPattern isInteger ifTrue:[
+            idx := itemsIndexOrLabelOrPattern
+        ] ifFalse:[
+            itemsIndexOrLabelOrPattern includesMatchCharacters ifTrue:[
+                idx := component indexOfElementForWhich:[:el | itemsIndexOrLabelOrPattern match:el label string].
+            ] ifFalse:[
+                idx := component indexOfElementForWhich:[:el | el label string = itemsIndexOrLabelOrPattern].
+            ].
+            idx == 0 ifTrue:[
+                self error:'no such item in hierarchicalList: ',itemsIndexOrLabelOrPattern
+            ].
+        ].
+        yPos := component yVisibleOfLine:idx.
+        self movePointerToComponent:component offset:(0 @ (yPos + 3)).
+        Delay waitForSeconds:0.5.
+        (component listAt:idx) expand.
+        ^ self
+    ].
+
+    self error:'cannot expand in this component'
+
+    "Created: / 19-07-2019 / 12:34:25 / Claus Gittinger"
+    "Modified (format): / 19-07-2019 / 14:55:34 / Claus Gittinger"
+!
+
 fastMoveTo:componentName
     "move the mouse to componentName without circling"
 
@@ -445,7 +602,7 @@
     "Modified: / 19-07-2019 / 15:38:11 / Claus Gittinger"
 !
 
-select:itemsLabel
+select:itemsIndexOrLabelOrPattern
     "select an item by label,
      allowed after moving to:
         aComboBox
@@ -467,8 +624,17 @@
         self movePointerToComponent:component menuButton.
         self click:1 inComponent:component menuButton.
         Delay waitForSeconds:0.3.
-        (idx := component list indexOf:itemsLabel ifAbsent:[nil]) isNil ifTrue:[
-            self error:'no such item in comboList: ',itemsLabel
+        itemsIndexOrLabelOrPattern isInteger ifTrue:[
+            idx := itemsIndexOrLabelOrPattern
+        ] ifFalse:[
+            itemsIndexOrLabelOrPattern includesMatchCharacters ifTrue:[
+                idx := component list findFirst:[:lbl | itemsIndexOrLabelOrPattern match:lbl]
+            ] ifFalse:[
+                idx := component list indexOf:itemsIndexOrLabelOrPattern.
+            ].
+            idx == 0 ifTrue:[
+                self error:'no such item in comboList: ',itemsIndexOrLabelOrPattern
+            ].
         ].
         component select:idx.
         Delay waitForSeconds:0.3.
@@ -478,13 +644,26 @@
         ^ self
     ].    
     (component isKindOf:HierarchicalListView) ifTrue:[
-        component 
-            selectElementForWhich:[:el |
-                el label string = itemsLabel
-            ] 
-            ifAbsent:[
-                self error:'no such item in hierarchicalList: ',itemsLabel
+        |idx yPos|
+
+        itemsIndexOrLabelOrPattern isInteger ifTrue:[
+            idx := itemsIndexOrLabelOrPattern
+        ] ifFalse:[
+            itemsIndexOrLabelOrPattern includesMatchCharacters ifTrue:[
+                idx := component indexOfElementForWhich:[:el | itemsIndexOrLabelOrPattern match:el label string].
+            ] ifFalse:[
+                idx := component indexOfElementForWhich:[:el | el label 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
     ].
 
@@ -533,7 +712,6 @@
         Delay waitForSeconds:0.5.
         widget select:itemsIndex.
         Delay waitForSeconds:0.5.
-self halt.
         ^ self
     ].    
     (widget isKindOf:SelectionInListView) ifTrue:[
@@ -775,51 +953,32 @@
     "find a component by name - in the active and possibly in any app.
      Can return either a view or a menu item"
     
-    |component candidates modalGroup|
-
-    application notNil ifTrue:[ 
-        (component := self findComponent:componentName in:application) notNil ifTrue:[
-            ^ component.
-        ].
-    ].
-    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
-        ].
-    ].
-    true "application isNil" ifTrue:[
-        "/ search through all current applications
-        WindowGroup scheduledWindowGroups do:[:eachWG |
-            |eachApp|
-
-            (eachApp := eachWG application) notNil ifTrue:[
-                component := self findComponent:componentName in:eachApp.
-                component notNil ifTrue:[ 
-                    candidates add:component
-                ].
-            ].
+    ^ self 
+        findComponent:componentName
+        inAllApplications:true
+        ifMultiple:[
+            self proceedableError:('multiple components found by name: ',componentName).
+            nil
         ].
 
-        candidates size == 1 ifTrue:[
-            ^ 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.
-        ].
-    ].
+    "
+     ShowMeHowItWorks basicNew findComponent:'Classes'
+     ShowMeHowItWorks basicNew findComponent:'Klassen'
+    "
 
-    ^ nil
+    "Created: / 19-07-2019 / 12:02:30 / Claus Gittinger"
+    "Modified: / 19-07-2019 / 16:44:30 / Claus Gittinger"
+    "Modified (comment): / 23-07-2019 / 09:32:44 / Claus Gittinger"
+!
+
+findComponent:componentName ifMultiple:exceptionalValue
+    "find a component by name - in the active and possibly in any app.
+     Can return either a view or a menu item"
+    
+    ^ self
+        findComponent:componentName
+        inAllApplications:true
+        ifMultiple:exceptionalValue
 
     "
      ShowMeHowItWorks basicNew findComponent:'Classes'
@@ -954,6 +1113,66 @@
     "Modified (comment): / 23-07-2019 / 09:31:34 / Claus Gittinger"
 !
 
+findComponent:componentName inAllApplications:inAllApplicationsBool ifMultiple:exceptionalValue
+    "find a component by name - in the active and possibly in any app.
+     Can return either a view or a menu item"
+    
+    |component candidates modalGroup|
+
+    application notNil ifTrue:[ 
+        (component := self findComponent:componentName in:application) notNil ifTrue:[
+            ^ component.
+        ].
+    ].
+    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
+        ].
+    ].
+    inAllApplicationsBool ifTrue:[
+        "/ search through all current applications
+        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:[
+            ^ candidates first
+        ].
+        candidates notEmpty ifTrue:[
+            "/ multiple elements (probably there are multiple topviews open...
+            "/ check the current windowGroup
+            ^ exceptionalValue value
+        ].
+    ].
+
+    ^ nil
+
+    "
+     ShowMeHowItWorks basicNew findComponent:'Classes'
+     ShowMeHowItWorks basicNew findComponent:'Klassen'
+    "
+
+    "Created: / 19-07-2019 / 12:02:30 / Claus Gittinger"
+    "Modified: / 19-07-2019 / 16:44:30 / Claus Gittinger"
+    "Modified (comment): / 23-07-2019 / 09:32:44 / Claus Gittinger"
+!
+
 screenBoundsOfComponent:aWidgetOrMenuItem
     aWidgetOrMenuItem isView ifTrue:[
         ^ aWidgetOrMenuItem screenBounds
@@ -1029,7 +1248,7 @@
     x := position x.
     y := position y.
     
-    self movePointerToScreenPosition:position.
+    "/ self movePointerToScreenPosition:position.
 
     false "OperatingSystem isOSXlike" ifTrue:[
         |osxPos|
@@ -1060,7 +1279,7 @@
     x := position x.
     y := position y.
     
-    self movePointerToScreenPosition:position.
+    "/ self movePointerToScreenPosition:position.
 
     false "OperatingSystem isOSXlike" ifTrue:[
         |osxPos|
@@ -1169,6 +1388,33 @@
     "Modified: / 23-07-2019 / 09:36:57 / Claus Gittinger"
 !
 
+movePointerToComponent:aWidgetOrMenuItem rightBottomOffset:offset
+    "move the mouse to position inside aWidget's.
+     Offset is from the rightBottom"
+    
+    |bounds|
+
+    bounds := self screenBoundsOfComponent:aWidgetOrMenuItem.
+    self movePointerToScreenPosition:(bounds corner - offset) rounded.
+
+    "Created: / 19-07-2019 / 16:18:58 / Claus Gittinger"
+    "Modified: / 23-07-2019 / 09:36:57 / Claus Gittinger"
+!
+
+movePointerToComponent:aWidgetOrMenuItem rightOffset:offset
+    "move the mouse to position inside aWidget's.
+     Offset is from the rightTop (i.e. x is subtracted, y is added)"
+    
+    |bounds pos|
+
+    bounds := self screenBoundsOfComponent:aWidgetOrMenuItem.
+    pos := (bounds right - offset x) @ (bounds top + offset y).
+    self movePointerToScreenPosition:pos 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"
 
@@ -1233,35 +1479,11 @@
     "must run as a separate process;
      otherwise - if started by the app itself -
      no events will be processed while running"
-
-    |wasActive|
-
-    (wasActive := ActiveHelp isActive) ifTrue:[
-        ActiveHelp stop.
-    ].
-    self prepare.
-
-    "/ run pnce in verifying mode
-    [
-        self verify:specArray.
-    ] ensure:[
-        wasActive ifTrue:[ActiveHelp start].
-    ].
+    |startLabel|
 
-    [
-        ActiveHelp stop.
-        [
-            Error handle:[:ex |
-                Dialog warn:(self class classResources 
-                                    stringWithCRs:'An error was encountered in the show:\\%1' 
-                                    with:ex description withCRs)
-            ] do:[
-                self doStream:(specArray readStream)
-            ].
-        ] ensure:[
-            wasActive ifTrue:[ActiveHelp start].
-        ].
-    ] fork.
+    startLabel := 'start'.
+    self do:specArray from:startLabel
+
 
     "
      ShowMeHowItWorks do:
@@ -1275,6 +1497,29 @@
     "Modified: / 25-07-2019 / 11:48:53 / Claus Gittinger"
 !
 
+do:specArray from:startLabelOrNil withUI:withUIBoolean
+    "must run as a separate process;
+     otherwise - if started by the app itself -
+     no events will be processed while running"
+
+    withUIBoolean ifTrue:[
+        ui := ShowMeHowItWorksRunner openOn:self.
+    ].
+    self do:specArray from:startLabelOrNil
+
+    "
+     ShowMeHowItWorks 
+        do:#(
+            (show: 'blah blah')
+            (moveTo: NameOfComponent)
+        )
+        withUI:true
+    "
+
+    "Created: / 23-07-2019 / 10:24:53 / Claus Gittinger"
+    "Modified: / 25-07-2019 / 11:48:53 / Claus Gittinger"
+!
+
 do:specArray withUI:withUIBoolean
     "must run as a separate process;
      otherwise - if started by the app itself -
@@ -1283,7 +1528,7 @@
     withUIBoolean ifTrue:[
         ui := ShowMeHowItWorksRunner openOn:self.
     ].
-    self do:specArray
+    self do:specArray from:nil
 
     "
      ShowMeHowItWorks 
@@ -1333,6 +1578,52 @@
 
 !ShowMeHowItWorks methodsFor:'running - private'!
 
+do:specArray from:startLabelOrNil
+    "must run as a separate process;
+     otherwise - if started by the app itself -
+     no events will be processed while running"
+
+    |wasActive|
+
+    (wasActive := ActiveHelp isActive) ifTrue:[
+        ActiveHelp stop.
+    ].
+    self prepare.
+
+    "/ run once in verifying mode
+    [
+        self verify:specArray.
+    ] ensure:[
+        wasActive ifTrue:[ActiveHelp start].
+    ].
+
+    [
+        ActiveHelp stop.
+        [
+            Error handle:[:ex |
+                Dialog warn:(self class classResources 
+                                    stringWithCRs:'An error was encountered in the show:\\%1' 
+                                    with:ex description withCRs)
+            ] do:[
+                self doStream:(specArray readStream) from:startLabelOrNil
+            ].
+        ] ensure:[
+            wasActive ifTrue:[ActiveHelp start].
+        ].
+    ] fork.
+
+    "
+     ShowMeHowItWorks do:
+        #(
+            (show: 'blah blah')
+            (moveTo: NameOfComponent)
+        )    
+    "
+
+    "Created: / 23-07-2019 / 10:24:53 / Claus Gittinger"
+    "Modified: / 25-07-2019 / 11:48:53 / Claus Gittinger"
+!
+
 doCommand:op
     "execute a single command"
     
@@ -1396,6 +1687,41 @@
 !
 
 doStream:specStream
+    self doStream:specStream from:nil
+    
+"<<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: / 23-07-2019 / 11:48:45 / Claus Gittinger"
+!
+
+doStream:specStream from:startLabelOrNil
     |previousStream resources|
 
     resources := self class classResources.
@@ -1405,9 +1731,32 @@
 
     previousStream := opStream.
     [
+        |nextCommand|
+
         opStream := specStream.
+        startLabelOrNil notNil ifTrue:[
+            |found|
+
+            "/ skip for that label
+            found := false.
+            [ found ] whileFalse:[
+                |cmd|
+
+                cmd := opStream nextOrNil.
+                cmd isNil ifTrue:[
+                    self proceedableError:'label not found: ',startLabelOrNil.
+                    ^ self.
+                ].
+                (cmd = 'label:') ifTrue:[
+                    found := (opStream next = startLabelOrNil)
+                ].
+            ].
+        ].
+
         [opStream atEnd] whileFalse:[
-            self nextCommand.
+            nextCommand := opStream next.
+            self doCommand:nextCommand.
+
             Display shiftDown ifTrue:[
                 (IntroShownCount ? 0) > 3 ifFalse:[
                     self tell:(self possiblyTranslate:'You pressed the SHIFT key.').
@@ -1458,41 +1807,6 @@
     "Modified: / 23-07-2019 / 11:48:45 / Claus Gittinger"
 !
 
-nextCommand
-    self doCommand:(opStream next).
-
-"<<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 / 15:35:15 / Claus Gittinger"
-!
-
 possiblyTranslate:aString
     translate ifTrue:[^ self class classResources string:aString].
     ^ aString