ShowMeHowItWorks.st
author Claus Gittinger <cg@exept.de>
Sat, 18 Jan 2020 21:14:48 +0100
changeset 3836 c50e15626fd8
parent 3828 d06d8ab3e5b8
child 3851 8251db282466
permissions -rw-r--r--
#OTHER by exept do not refer to XPToolbarIconLibrary - ask ToolbarIconLibrary

"{ Encoding: utf8 }"

"
 COPYRIGHT (c) 2018 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:libtool2' }"

"{ NameSpace: Smalltalk }"

Object subclass:#ShowMeHowItWorks
	instanceVariableNames:'application opStream streamStack lastComponentName lastComponent
		lastResult voice translate language verifying
		closeApplicationWhenFinished defaultComponentWaitTime ui
		theShowFile defaultElementTimeout applicationStack pronunciations'
	classVariableNames:'IntroShownCount DebugMode StartLabel'
	poolDictionaries:''
	category:'Interface-Help'
!

Object subclass:#ItemInView
	instanceVariableNames:'view item boundsInView'
	classVariableNames:''
	poolDictionaries:''
	privateIn:ShowMeHowItWorks
!

!ShowMeHowItWorks class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 2018 by eXept Software AG
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

documentation
"
    automatic presentations.
    To see how it works, open a methodFinder:
        MethodFinderWindow open
    and select its 'Show me how it works' item in the help menu.

    [author:]
        Claus Gittinger
"
!

example
    MethodFinderWindow open.
    
    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'
        ))
    )
!

example2
    ShowMeHowItWorks do:#(
        language: 'de'
        (pronounce:'feature' as:'fiedscher')
        (pronounce:'suite' as:'swied')
        (pronounce:'expecco' as:'expecko')
        (pronounce:'read me' as:'ried mie')
        (pronounce:'python' as:'paiton')
        (pronounce:'jenkins' as:'dschengkins')
        show:'jenkins'
        show:'dies ist ein neues feature'
        show:'suite'
        show:'expecco'
        show:'read me'
        show:'python'
        show:'node'
    )
!

scriptFormat
"

    speaking:
        show:           'text'

    mouse movement:
        moveTo:         '<component>'       move there and circle around
        fastMoveTo:     '<component>'       move without circling
        (drag: '<fromComponent>' toComponent: '<toComponent>' dropAt:<pos> show:'text')
            pos is one of #topLeft, #topCenter, #topRght, #bottomLeft, #bottomCenter, #bottomRight
            text is spoken before the drop

    mouse actions:
        click
        click:          buttonNumber
        select:         itemsIndexOrLabelOrPattern
            itemsIndexOrLabelOrPattern can be an integer or text

    misc:
        wait:           seconds             just pause

        label:          'name'              for debugging, presentation can be started at some
                                            label, by setting the StartLabel class variable

        (waitFor: '<component>' timeout:seconds)
                                            wait for a component to appear

        open            'nameOfAppModelClass'
                                            open an application (class)

        raise           'application'
        raise           'masterApplication'

        intro           

component naming:
    '<component>' can be:
        name of a component, as defined in the UI-spec's nameKey,
        helpKey
        title
        label

     a path like 'ProjectTree/ToolBar' will first find ProjectTrue, then ToolBar
     a menu item index like 'Toolbar/item[idx]'


"
! !

!ShowMeHowItWorks class methodsFor:'running'!

application:anApplicationOrNilForAll do:specArray 
    "spec contains a list of action commands (show: / moveTo: etc.)"

    self new 
        application:anApplicationOrNilForAll;
        do:specArray 

    "
     ShowMeHowItWorks do:
        #(
            (language: de)
            (show: 'üben üben üben')
            (wait: 0.5)
            (moveTo: NameOfComponent)
        )    
    "

    "Created: / 19-07-2019 / 10:52:59 / Claus Gittinger"
    "Modified (comment): / 23-07-2019 / 10:26:42 / Claus Gittinger"
!

application:anApplicationOrNilForAll doShowFile:aFilename 
    "spec contains a list of action commands (show: / moveTo: etc.)"

    self new 
        application:anApplicationOrNilForAll;
        doShowFile:aFilename 

    "
     ShowMeHowItWorks do:
        #(
            (language: de)
            (show: 'üben üben üben')
            (wait: 0.5)
            (moveTo: NameOfComponent)
        )    
    "

    "Created: / 19-07-2019 / 10:52:59 / Claus Gittinger"
    "Modified (comment): / 23-07-2019 / 10:26:42 / Claus Gittinger"
!

do:specArray
    "spec contains a list of action commands (show: / moveTo: etc.)"

    self do:specArray withUI:false

    "
     ShowMeHowItWorks do:
        #(
            (language: de)
            (show: 'üben üben üben')
            (wait: 0.5)
            (moveTo: NameOfComponent)
        )    
    "

    "Created: / 19-07-2019 / 10:52:59 / Claus Gittinger"
    "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.)"

    self new do:specArray 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"
! !

!ShowMeHowItWorks methodsFor:'accessing'!

application:anApplication
    "if set, only that application is presented (widget search is limited to that one)"

    application := anApplication.
!

setLanguage:lang
    voice := OperatingSystem bestVoiceForLanguage:lang.
    language := lang.

    "Created: / 23-07-2019 / 10:27:02 / Claus Gittinger"
! !

!ShowMeHowItWorks methodsFor:'commands'!

closeApplication
    <action>

    application closeRequest.
!

freezePin:pinName ofStep:stepName with:freezeString
    <action>

    |diagramEditor pinPO|

    (diagramEditor := lastComponent) isScrollWrapper ifTrue:[
        diagramEditor := lastComponent scrolledView
    ].
    pinPO := self pinPOForPin:pinName ofStep:stepName. 

    diagramEditor selection:{ pinPO }.
    diagramEditor doubleClickOn:pinPO.
    self fastMoveTo:(lastComponentName,'/','EditField').
    self click.
    self clear.
    self type:freezeString.
    self key:#Return.
!

intro
    <action>

    verifying ifTrue:[^ self].

    IntroShownCount := (IntroShownCount ? 0).

    "/ only say this a few times..
    IntroShownCount > 3 ifTrue:[^ self].
    IntroShownCount := IntroShownCount + 1.

    language isNil ifTrue:[
        self setLanguage:(Smalltalk language). 
    ].
    self tell:(self class classResources 
                string:'You can stop this show, by pressing the SHIFT key').

    "Created: / 19-07-2019 / 15:49:19 / Claus Gittinger"
    "Modified: / 23-07-2019 / 11:06:13 / Claus Gittinger"
!

label:nameOfLabel
    <action>

    "/ skipped here; see goto
!

language:lang
    <action>

    self setLanguage:lang.
    translate := false.

    "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>
    
    Dialog information:(self class classResources 
                            stringWithCRs:'Show Paused.\Click on "OK" to proceed')

    "Created: / 19-07-2019 / 15:03:17 / Claus Gittinger"
    "Modified: / 19-07-2019 / 16:13:33 / Claus Gittinger"
!

popApplication
    <action>

    "go back to the previous app"

    application := applicationStack removeLast.
!

pronounce:word as:pronunciation
    <action>

    pronunciations isNil ifTrue:[
        pronunciations := OrderedDictionary new.
    ].
    pronunciations at:word put:pronunciation
!

pushApplication:newApplicationOrName
    <action>

    "goto another app, remembering where we were before"

    |newApplication|

    (newApplication := newApplicationOrName) isString ifTrue:[
        newApplication := self findApplication:newApplicationOrName ifMultiple:nil.
        self assert: newApplication notNil.
    ].

    applicationStack isNil ifTrue:[applicationStack := OrderedCollection new].

    applicationStack add:application.
    self application:newApplication.
!

raise:what
    <action>

    verifying ifTrue:[^ self].

    what == #application ifTrue:[
        application topView raise.
        ^ self
    ].
    what == #masterApplication ifTrue:[
        application windowGroup isModal ifTrue:[
            application windowGroup mainGroup topViews first raise
        ] ifFalse:[
            (application masterApplication ? application) topViews first raise.
        ].
        ^ self
    ].
    self halt.
!

selectPin:pinName ofStep:stepName
    <action>

    |diagramEditor pinPO count|

    (diagramEditor := lastComponent) isScrollWrapper ifTrue:[
        diagramEditor := lastComponent scrolledView
    ].

    count := 0.
    [
        Delay waitForSeconds:0.1.
        pinPO := self pinPOForPin:pinName ofStep:stepName.
    ] doWhile:[pinPO isNil and:[ (count := count+1) < 20]].

    self movePointerToComponent:diagramEditor offset:(pinPO frame center).
    diagramEditor selection:{ pinPO }. 
!

show:message
    "showing (and speak) some message."

    <action>
    
    self showing:message saying:nil do:nil

    "Created: / 19-07-2019 / 15:59:18 / Claus Gittinger"
    "Modified (comment): / 19-07-2019 / 18:54:36 / Claus Gittinger"
!

show:message for:seconds
    "showing (and speak) some message and wait for some time."

    <action>

    DebugMode == true ifTrue:[^ self].

    self show:message.
    self wait:seconds.

    "Created: / 19-07-2019 / 18:54:20 / Claus Gittinger"
!

show:message saying:sentenceOrNil
    "showing (and speak) some message."

    <action>
    
    self showing:message saying:sentenceOrNil do:nil

    "Created: / 19-07-2019 / 15:59:18 / Claus Gittinger"
    "Modified (comment): / 19-07-2019 / 18:54:36 / Claus Gittinger"
!

show:message saying:sentenceOrNil for:seconds
    "showing (and speak) some message and wait for some time."

    <action>
    
    DebugMode == true ifTrue:[^ self].
    self show:message saying:sentenceOrNil.
    self wait:seconds.

    "Created: / 19-07-2019 / 18:54:20 / Claus Gittinger"
!

showing:message do:operationsOrNothing
    "execute operationsOrNothing while showing (and speaking) some message."

    <action>
    
    self showing:message saying:nil do:operationsOrNothing
!

showing:message saying:sentenceOrNil do:operationsOrNothing
    "execute operationsOrNothing while showing (and speaking) some message."

    <action>
    
    |xLatedMessage messageView talkDone|

    xLatedMessage := (translate and:[application notNil]) 
                            ifTrue:[application resources stringWithCRs:message]
                            ifFalse:[message withCRs].
    
    self assert:(operationsOrNothing isNil or:[operationsOrNothing isSequenceable]).

    messageView := ActiveHelpView for:xLatedMessage.
    "/ messageView shapeStyle:#cartoon.
    [
        |p top left bot right w h screen screenBounds|

        w := messageView preferredExtent x.
        h := messageView preferredExtent y.

        screen := Screen current.

        p := screen pointerPosition.
        screenBounds := screen monitorBoundsAt:p.

        left := p x. top := p y.
        "/ try not to cover the thing we want to describe
        left := left + 50.
        top := top + 50.

        bot := top + h. right := left + w.

        right > screenBounds right ifTrue:[
            right := screenBounds right.
            left := right - w.
        ].
        bot > screenBounds bottom ifTrue:[
            bot := screenBounds bottom.
            top := bot - h.
        ].

        messageView origin:(left @ top).
        messageView makeFullyVisible.
        messageView realize.

        self talking ifTrue:[
            talkDone := Semaphore new.
            [
                |sentence|
                sentence := (sentenceOrNil ? xLatedMessage) copy withCRs replaceAny:c'\t' with:(Character space).
                sentence asStringCollection do:[:eachLineIn |
                    |eachLine|

                    eachLine := eachLineIn.
                    "/ eachLine := eachLine copy withCRs replaceAny:c'\n\r' with:(Character space).
                    eachLine := eachLine asLowercase.
                    pronunciations notNil ifTrue:[
                        pronunciations keysAndValuesDo:[:word :pronounced |
                            (eachLine includesString:word) ifTrue:[
                                eachLine := eachLine copyReplaceString:word withString:pronounced
                            ]
                        ]
                    ].
                    self tell:eachLine.
                ].
                talkDone signal
            ] fork.
            
            "/
            "/ allow speaker some headoff
            verifying ifFalse:[
                Delay waitForSeconds:(xLatedMessage size / 15)+0.5.
            ].
        ].

        operationsOrNothing notEmptyOrNil ifTrue:[
            self doStream:(operationsOrNothing readStream).
        ].
    ] ensure:[
        messageView destroy
    ].
    self talking ifTrue:[
        talkDone wait.
    ].

    "Created: / 19-07-2019 / 11:19:27 / Claus Gittinger"
    "Modified: / 23-07-2019 / 10:52:30 / Claus Gittinger"
!

thankyou
    <action>
    
    self show:(self randomThankYou).

    "Created: / 23-07-2019 / 10:50:43 / Claus Gittinger"
!

timeout:seconds
    <action>

    defaultElementTimeout := seconds.

    "Created: / 23-07-2019 / 10:27:02 / Claus Gittinger"
!

wait:seconds
    <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 component|

    verifying ifTrue:[^ self].

    lastComponentName := componentName.
    endTime := Timestamp now + seconds.
    [   
        (component := self findComponent:componentName inAllApplications:true ifMultiple:nil) notNil ifTrue:[
            lastComponent := component.
            ^ self
        ].
        Delay waitForSeconds:0.1.
        Timestamp now > endTime ifTrue:[
            self error:('component %1 not present after %2' bindWith:componentName with:seconds)
        ]
    ] loop
! !

!ShowMeHowItWorks methodsFor:'commands - checking'!

isEmpty:componentName
    "true if the addressed textview/field is empty.
     Leaves component in lastComponent"

    <action>

    |component|
    
    component := self componentNamed:componentName.
    component isScrollWrapper ifTrue:[ component := component scrolledView ].
    component isTextView ifTrue:[
        ^ component contents isEmptyOrNil
    ].
    self error:'isEmpty: unhandled (non-text) component type: ',component displayString.

    "Created: / 19-07-2019 / 15:33:47 / Claus Gittinger"
!

unless:query do:actions
    <action>

    |result|
    
    result := self doCommand:query.
    result ifFalse:[
        self doCommand:actions
    ].

    "Created: / 19-07-2019 / 15:33:32 / Claus Gittinger"
! !

!ShowMeHowItWorks methodsFor:'commands - mouse & keyboard'!

clear
    "clear entry fields of last component"

    <action>

    verifying ifTrue:[^ self].

    lastComponent contents:nil
!

click
    "press-release in last component"
    
    <action>

    ^ self click:1 inComponent:lastComponent

    "Created: / 19-07-2019 / 16:11:03 / Claus Gittinger"
!

click:buttonNr
    "press-release in last component"
    
    <action>

    self assert:(buttonNr isInteger).
    ^ self click:buttonNr inComponent:lastComponent

    "Created: / 19-07-2019 / 13:21:20 / Claus Gittinger"
    "Modified: / 19-07-2019 / 16:10:19 / Claus Gittinger"
!

click:buttonNr clickTime:t
    "press-release in last component"
    
    <action>

    self assert:(buttonNr isInteger).
    ^ self click:buttonNr inComponent:lastComponent clickTime:t
!

clickIn:componentName
    "press-release.
     Leaves component in lastComponent"
    
    <action>

    ^ self click:1 inComponent:(self componentNamed:componentName)

    "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>

    self drag:itemsIndexOrLabelOrPattern toComponent:targetComponentName dropAt:where show:nil
!

drag:itemsIndexOrLabelOrPattern toComponent:targetComponentName dropAt:where show:textWhenDroppingOrNil
    "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 dNdMgr|

    verifying ifTrue:[^ self].

    targetComponent := self findComponent:targetComponentName.
    self assert:targetComponent notNil.

    ((sourceComponent := lastComponent) isKindOf:ScrollableView) ifTrue:[
        sourceComponent := sourceComponent scrolledView.
    ].

    targetPos := self where:where inComponent:targetComponent. 

    (sourceComponent isKindOf:HierarchicalListView) ifTrue:[

        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 fromView:nil toView:sourceComponent.

        targetPosOnScreen := Display translatePoint:targetPos fromView:targetComponent toView:nil.
        targetPosInSource := Display translatePoint:targetPosOnScreen fromView:nil toView:sourceComponent.

        sourceComponent simulateButtonPress:1 at:sourcePos sendDisplayEvent:false.
        sourceComponent simulateButtonPress:1 at:sourcePos sendDisplayEvent:true.
        Delay waitForSeconds:0.2.

        dNdMgr := sourceComponent startDragAt:sourcePos.

        textWhenDroppingOrNil notNil ifTrue:[
            self show:textWhenDroppingOrNil.
        ].

        Delay waitForSeconds:1.
        self movePointerToComponent:targetComponent offset:targetPos.
        Delay waitForSeconds:1.
        dNdMgr buttonMotion:1 x:targetPos x y:targetPos y view:targetComponent.

        dNdMgr buttonRelease:1 x:targetPos x y:targetPos y view:targetComponent.
        sourceComponent simulateButtonRelease:1 at:targetPos sendDisplayEvent:false.
        ^ 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"
!

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.
     Leaves component in lastComponent"

    <action>

    |component|

    component := self componentNamed:componentName.
    self movePointerToComponent:component speed:(self pointerMoveSpeedFast).

    "
     ShowMeHowItWorks basicNew fastMoveTo:'Classes'
     ShowMeHowItWorks basicNew fastMoveTo:'Klassen'
    "

    "Created: / 19-07-2019 / 15:39:23 / Claus Gittinger"
    "Modified: / 20-07-2019 / 08:14:16 / Claus Gittinger"
    "Modified (comment): / 23-07-2019 / 09:33:31 / Claus Gittinger"
!

key:aKeySymbol
    "press/release a key in last component"

    <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.
     Leaves component in lastComponent"

    <action>

    |component|

    component := self componentNamed:componentName.
    self movePointerToComponent:component.
    Display ctrlDown ifTrue:[^ self].
    self circlePointerAroundComponent:component.

    "Created: / 19-07-2019 / 11:20:42 / Claus Gittinger"
    "Modified: / 19-07-2019 / 15:38:11 / Claus Gittinger"
!

press:buttonNr
    <action>

    "press at the current position in last component"

    self assert:(buttonNr isInteger).
    ^ self press:buttonNr inComponent:lastComponent.

"/    "press at the current position"
"/
"/    |position screen x y|
"/
"/    verifying ifTrue:[^ self].
"/
"/    screen := Screen current.
"/    position := screen pointerPosition.
"/    x := position x.
"/    y := position y.
"/
"/    "/ self movePointerToScreenPosition: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"
"/    "Modified: / 23-07-2019 / 09:38:31 / Claus Gittinger"
!

release:buttonNr
    <action>

    "release at the current position in last component"
    
    <action>

    self assert:(buttonNr isInteger).
    ^ self press:buttonNr inComponent:lastComponent.
"/    |position screen x y|
"/
"/    verifying ifTrue:[^ self].
"/
"/    screen := Screen current.
"/    position := screen pointerPosition.
"/    x := position x.
"/    y := position y.
"/    
"/    self movePointerToScreenPosition: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"
    "Modified: / 23-07-2019 / 09:38:38 / Claus Gittinger"
!

scrollToEnd
    <action>

    |scrollWrapper|

    "/ look for a scrollable view along the superview hierarchy
    scrollWrapper := lastComponent 
        allSuperViewsDetect:[:v | v isScrollWrapper]
        ifNone:nil.

    scrollWrapper isNil ifTrue:[
        "/ look in subviews for the first scrollWrapper
        scrollWrapper := lastComponent 
            allSubViewsDetect:[:v | v shown and:[v isScrollWrapper]]
            ifNone:nil.
    ].
    scrollWrapper notNil ifTrue:[
        scrollWrapper scrollBar scrollToEnd
    ].
!

select:itemsIndexOrLabelOrPattern
    "select an item by label,
     allowed after moving to:
        a ComboBox
        a SelectionInListView
        a NoteBook
        a DSVColumnView
    "    

    <action>

    |idx component yPos tab|

    verifying ifTrue:[^ self].

    ((component := lastComponent) isKindOf:ScrollableView) ifTrue:[
        component := component scrolledView.
    ].

    (component isKindOf:ComboView) ifTrue:[
        "/ click on the menubutton
        self movePointerToComponent:component menuButton.
        self click:1 inComponent:component menuButton.
        Delay waitForSeconds:0.3.
        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.
        component shownMenu notNil ifTrue:[
            component shownMenu hide.
        ].    
        ^ self
    ].    
    (component isKindOf:HierarchicalListView) ifTrue:[
        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
    ].
    (component isKindOf:SelectionInListModelView) ifTrue:[
        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 click.
            ^ self
        ].
    ].

    (component isKindOf:NoteBookView) ifTrue:[
        (idx := itemsIndexOrLabelOrPattern) isInteger ifFalse:[
            idx := component list
                    findFirst:[:eachNotebookTab| 
                        eachNotebookTab label string = itemsIndexOrLabelOrPattern string
                        or:[ eachNotebookTab printableLabel string = itemsIndexOrLabelOrPattern string ]
                    ].
            "/ idx := component indexOfTabNamed:itemsIndexOrLabelOrPattern
        ].
        tab := component tabAtIndex:idx.
        self movePointerToComponent:component offset:(tab layout center).
        component selection:idx.
        ^ self
    ].
    (component isKindOf:DSVColumnView) ifTrue:[
        (idx := itemsIndexOrLabelOrPattern) isInteger ifFalse:[
            self halt.
        ].
        yPos := ((component yVisibleOfRowNr:idx) + (component yVisibleOfRowNr:idx+1)) // 2.
        self movePointerToComponent:component offset:(1 @ yPos).
        component selectRow:idx.
        ^ self
    ].

    self error:('cannot select "%1" this component: %2' bindWith:itemsIndexOrLabelOrPattern with:component className)

    "Created: / 19-07-2019 / 12:34:25 / Claus Gittinger"
    "Modified (format): / 19-07-2019 / 14:55:34 / Claus Gittinger"
!

selectComponent:componentName
    "Leaves component in lastComponent without moving"

    <action>

    |component|

    component := self componentNamed:componentName.
!

selectIndex:itemsIndex
    "select an item by index,
     allowed after moving to:
        aComboBox
        aSelectionInListView
    "    

    <action>

    self selectIndex:itemsIndex in:lastComponent

    "Created: / 19-07-2019 / 14:20:11 / Claus Gittinger"
    "Modified: / 19-07-2019 / 21:59:36 / Claus Gittinger"
!

selectIndex:itemsIndex in:widgetArg
    "select an item by index,
     allowed after moving to:
        aComboBox
        aSelectionInListView
    "    

    <action>

    |widget y offset possibleWidgets|

    verifying ifTrue:[^ self].

    (widget := widgetArg) isScrollWrapper ifTrue:[
        widget := widget scrolledView
    ].
    
    (widget isKindOf:ComboView) ifTrue:[
        "/ click on the menubutton
        self movePointerToComponent:widget menuButton.
        self click:1 inComponent:widget menuButton.
        Delay waitForSeconds:0.5.
        widget select:itemsIndex.
        Delay waitForSeconds:0.5.
        ^ self
    ].    
    (widget isKindOf:SelectionInListView) ifTrue:[
        (widget isLineVisible:itemsIndex) ifFalse:[
            widget scrollToLine:itemsIndex
        ].    
        "/ click on the item
        y := widget yOfLine:itemsIndex.
        offset := (widget width // 2) @ y.
        self movePointerToComponent:widget offset:offset.
        widget simulateButtonPress:1 at:offset sendDisplayEvent:false.
        Delay waitForSeconds:(self clickTime).
        widget simulateButtonRelease:1 at:offset sendDisplayEvent:false.
        Delay waitForSeconds:0.5.
        ^ self
    ].
    (widget isKindOf:SelectionInListModelView) ifTrue:[
        (widget isLineVisible:itemsIndex) ifFalse:[
            widget scrollToLine:itemsIndex
        ].    
        y := widget yVisibleOfLine:itemsIndex.
        offset := (widget width // 2) @ y.
        self movePointerToComponent:widget offset:offset.
        widget simulateButtonPress:1 at:offset sendDisplayEvent:false.
        Delay waitForSeconds:(self clickTime).
        widget simulateButtonRelease:1 at:offset sendDisplayEvent:false.
        Delay waitForSeconds:0.5.
        ^ self
    ].

    "/ none of it - see what is in there
    possibleWidgets := OrderedCollection new.
    widget allSubViewsDo:[:each |
        ((each isKindOf:ComboView) 
          or:[(each isKindOf:SelectionInListView)
          or:[(each isKindOf:SelectionInListModelView)
        ]]) ifTrue:[
            possibleWidgets add:each
        ]
    ].
    possibleWidgets size == 1 ifTrue:[
        self selectIndex:itemsIndex in:(possibleWidgets first).
        ^ self
    ].    
    
    self error:'cannot select this component'

    "Created: / 19-07-2019 / 21:59:15 / Claus Gittinger"
    "Modified: / 20-07-2019 / 07:57:41 / Claus Gittinger"
!

type:aString
    "enter text into the last component"

    <action>

    |t|

    verifying ifTrue:[^ self].

    t := Display ctrlDown ifTrue:[0.05] ifFalse:[0.1].
    lastComponent 
        simulateTextInput:aString at:(lastComponent extent // 2) 
        sendDisplayEvent:false keyPressTime:t

    "Created: / 19-07-2019 / 15:50:40 / 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"

    ^ self shortClickTime

    "Created: / 19-07-2019 / 13:17:20 / Claus Gittinger"
    "Modified: / 19-07-2019 / 15:21:51 / Claus Gittinger"
!

longClickTime
    "when clicking buttons"

    ^ 500 milliseconds

    "Created: / 19-07-2019 / 15:21:42 / 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"
!

pointerMoveSpeedFast
    ^ 600.   "/ pixels per second

    "Created: / 20-07-2019 / 08:13:58 / Claus Gittinger"
!

shortClickTime
    "when clicking"

    ^ 100 milliseconds

    "Created: / 19-07-2019 / 15:21:29 / Claus Gittinger"
!

talking
    "/ DebugMode := true
    verifying ifTrue:[^ false].
    DebugMode == true ifTrue:[^ false].

    "/ ^ Expecco::ExpeccoPreferences current speechEffectsEnabled
    ^ true

    "Created: / 19-07-2019 / 14:31:14 / Claus Gittinger"
    "Modified (comment): / 23-07-2019 / 09:45:35 / Claus Gittinger"
! !

!ShowMeHowItWorks methodsFor:'helper'!

randomThankYou
    ^ #(
        'thank you, for watching'
        'thank you for watching'
        'thank you'
        'thanks'
        'have a good day'
        'have a nice day'
        'have fun'
        'have fun with expecco'
        'have fun with expecco, by the way: expecco comes from the latin word: peccare, which means: "to sin"'
        'happy hacking'
        'happy hacking, I hope you liked what you saw'
        'hope you liked it'
        'see you again'
        'be the source with you'
        'be the force with you'
        'may the force be with you'
        'may the source be with you'
        'please give feedback, and let us know, if you liked it'
        'if you have any questions, please contact exept'
        'if you need more information, please take a look at the wiki'
    ) atRandom

    "
     OperatingSystem speak:'may the source be with you'
     OperatingSystem speak:'have fun with expecco'
     OperatingSystem speak:'have fun with expecco, by the way: expecco comes from the latin word: peccare, which means: to sin'
     OperatingSystem speak:'happy hacking, I hope you liked what you saw'
     OperatingSystem speak:'please give feedback, and let us know, if you liked it'
     OperatingSystem speak:'if you have any questions, please contact exept'
     OperatingSystem speak:'if you need more information, please take a look at the wiki'
    "

    "Created: / 19-07-2019 / 21:39:18 / Claus Gittinger"
!

tell:message
    self talking ifTrue:[
        OperatingSystem speak:message voiceName:voice.
    ].

    "Created: / 19-07-2019 / 14:57:50 / Claus Gittinger"
    "Modified: / 23-07-2019 / 10:28:02 / Claus Gittinger"
! !

!ShowMeHowItWorks methodsFor:'helpers - broken'!

click:buttonNr atPosition:position
    "press-release at position"
    
    |screen|

    verifying ifTrue:[^ self].

    screen := Screen current.
    
    screen setPointerPosition:position.    
    screen flush.
    self click:buttonNr

    "Created: / 19-07-2019 / 13:14:51 / Claus Gittinger"
! !

!ShowMeHowItWorks methodsFor:'helpers - component search'!

chooseBestComponentFrom:foundItems
    |reallyShown inMyApp|

    foundItems size == 1 ifTrue:[^ foundItems first].
    foundItems size == 0 ifTrue:[^ nil].

    reallyShown := foundItems select:[:item | item isReallyShown]. 
    reallyShown size == 1 ifTrue:[^ reallyShown first].
    reallyShown size == 0 ifTrue:[^ nil].

    "/ is there one in my application
    application notNil ifTrue:[
        inMyApp := reallyShown select:[:item | item topView application == application].
        inMyApp size == 1 ifTrue:[^ inMyApp first].
    ].
    "/ there seems to be a mapped view, but underneath
    "/ select the last one found
    ^ foundItems last.
!

componentNamed:componentName
    "retrieve a component by name or report an error if not found.
     Can return either a view or a menu item"

    |component|

    verifying ifFalse:[
        self waitFor:componentName timeout:(defaultElementTimeout ? 1).
        ^ lastComponent.
    ].

    lastComponentName := componentName.

    component := self findComponent:componentName.
    component isNil ifTrue:[
        self error:'no component found for: ',componentName mayProceed:verifying.
        ^ nil
    ].
    lastComponent := component.
    ^ component

    "Created: / 19-07-2019 / 15:37:35 / Claus Gittinger"
    "Modified (comment): / 23-07-2019 / 09:31:53 / Claus Gittinger"
!

findApplication:classNameOrWindowTitle ifMultiple:exceptionalValue
    "find an application by name"
    
    |candidates|

    candidates := IdentitySet new.

    "/ search through all current applications
    WindowGroup scheduledWindowGroups do:[:eachWG |
        |eachApp|

        (eachApp := eachWG application) notNil ifTrue:[
            ((classNameOrWindowTitle match:eachApp className) 
            or:[(classNameOrWindowTitle match:eachApp class nameWithoutPrefix)
            or:[(classNameOrWindowTitle match:eachApp mainWindow label)]]) ifTrue:[
                candidates add:eachApp
            ].
        ].
    ].
    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 findApplication:'Inspector*' ifMultiple: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
    "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:[
            self proceedableError:('multiple components found by name: ',componentName).
            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"
!

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'
     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"
!

findComponent:componentNameOrPath in:anApplicationOrViewOrMenuItem
    "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"

    |quoted idxString idx app window component componentNameSymbol 
     foundByName foundByHelpKey foundByTitle foundByLabel item
     checkIfAllMenuItemsDoTheSame|

    "/ 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
            ]
        ]
    ].

    (anApplicationOrViewOrMenuItem isKindOf:ItemInView) ifTrue:[
        anApplicationOrViewOrMenuItem isMenuItem ifTrue:[
            ^ self findComponent:componentNameOrPath in:anApplicationOrViewOrMenuItem item submenu
        ].
    ].

    (componentNameOrPath startsWith:'item[') ifTrue:[
        idxString := (componentNameOrPath withoutPrefix:'item[') withoutSuffix:']'.
        (idxString conform:#isDigit) ifTrue:[
            idx := Integer readFrom:idxString.
        ] ifFalse:[
            idxString := idxString withoutQuotes.
            idx := nil.
        ].

        (anApplicationOrViewOrMenuItem isKindOf:NoteBookView) ifTrue:[
            idx isNil ifTrue:[
                idxString includesMatchCharacters ifTrue:[
                    idx := anApplicationOrViewOrMenuItem list 
                                findFirst:[:aTab| (aTab label matches:idxString)
                                                    or:[aTab printableLabel matches:idxString]].
                ] ifFalse:[
                    idx := anApplicationOrViewOrMenuItem listIndexOf:idxString
                ].
            ].
            idx notNil ifTrue:[
                self halt.
            ]
        ] ifFalse:[
            (anApplicationOrViewOrMenuItem askFor:#isMenu) ifFalse:[
                self assert:false message:'container is not a menu'.
            ].
            "/ a menu item
            idx notNil ifTrue:[
                item := anApplicationOrViewOrMenuItem itemAtIndex:idx.
            ] ifFalse:[
                idxString includesMatchCharacters ifTrue:[
                    item := 
                        anApplicationOrViewOrMenuItem itemForWhich:[:item |
                            (idxString match:(item nameKey ? '') )
                            or:[ (idxString match:(item textLabel ? '')) 
                            or:[ (item value isSymbol and:[idxString match:(item value)])]]
                        ]  
                ] ifFalse:[
                    item := 
                        anApplicationOrViewOrMenuItem itemForWhich:[:item |
                            item nameKey = idxString
                            or:[ item textLabel = idxString 
                            or:[ (item value isSymbol and:[ item value = idxString ])]]
                        ]
                ].
            ].
            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:[
        (anApplicationOrViewOrMenuItem isKindOf:NoteBookView) ifFalse:[
            self assert:false message:'container is not a notebook'.
        ].
        idxString := (componentNameOrPath withoutPrefix:'tab[') withoutSuffix:']'.
        (idxString conform:#isDigit) ifTrue:[
            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].
                item isNil ifTrue:[
                    item := anApplicationOrViewOrMenuItem tabForWhich:[:tab | tab activeHelpKey = idxString].
                ].
            ].
        ].
        self assert:item notNil.
        ^ ItemInView new view:anApplicationOrViewOrMenuItem item:item boundsInView:(item layout)
    ].

    componentNameSymbol := componentNameOrPath asSymbolIfInterned ? componentNameOrPath.

    (app := anApplicationOrViewOrMenuItem) isView ifTrue:[
        window := anApplicationOrViewOrMenuItem.
        app := anApplicationOrViewOrMenuItem application
    ].
    app isApplicationModel ifTrue:[
        (component := app componentAt:componentNameSymbol) notNil ifTrue:[^ component].
        window := window ? app window.
    ] ifFalse:[
        (app askFor:#isMenuItem) ifTrue:[
            window := app submenu.
        ]
    ].
    window notNil ifTrue:[
        window shown ifFalse:[^ nil].
    ].

    "/ 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. 
    foundByHelpKey := OrderedCollection new. 
    foundByTitle := OrderedCollection new. 
    foundByLabel := OrderedCollection new.

    window withAllSubViewsDo:[:each |
        |foundIt|
        
        foundIt := false.
        each shown ifTrue:[
            {
                #name . foundByName .
                #helpKey . foundByHelpKey .
                #title . foundByTitle .
                #label . foundByLabel .
            } pairWiseDo:[:attr :coll |
                foundIt ifFalse:[
                    [
                        (each perform:attr) = componentNameSymbol ifTrue:[ 
                            coll add:each. foundIt := true
                        ]
                    ] on:MessageNotUnderstood do:[:ex | ].
                ]
            ].
            foundIt ifFalse:[
                each isMenu ifTrue:[
                    |comp|

                    (item := each detectItemForNameKey:componentNameSymbol) notNil ifTrue:[
                        comp := ItemInView new view:each item:item boundsInView:(item layout).
                        foundByName add:comp. foundIt := true
                    ].
                    foundIt ifFalse:[
                        (item := each detectItemForKey:componentNameSymbol) notNil ifTrue:[
                            comp := ItemInView new view:each item:item boundsInView:(item layout).
                            foundByName add:comp. foundIt := true 
                        ].    
                        foundIt ifFalse:[
                            (item := each detectItemForLabel:componentNameSymbol) notNil ifTrue:[
                                comp := ItemInView new view:each item:item boundsInView:(item layout).
                                foundByLabel add:comp. 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 view shown].
                    visibleItems notEmpty ifTrue:[
                        ^ visibleItems first
                    ].
                    ^ itemsFound first.
                ].
            ].
        ].

    foundByName notEmpty ifTrue:[
        checkIfAllMenuItemsDoTheSame value:foundByName.
        foundByName size > 1 ifTrue:[
            |item|

            (item := self chooseBestComponentFrom:foundByName) notNil ifTrue:[
                ^ item
            ].
        ].
        self assert:(foundByName size == 1) message:('multiple components found by name: %1' bindWith:componentNameOrPath).
        ^ foundByName first.
    ].
    foundByHelpKey notEmpty ifTrue:[
        checkIfAllMenuItemsDoTheSame value:foundByHelpKey.
        self assert:(foundByHelpKey size == 1) message:'multiple components found by helpKey'.
        ^ foundByHelpKey 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: %1' bindWith:componentNameOrPath).
        ^ foundByLabel first.
    ].
    ^ component

    "
     self basicNew findComponent:'Klassen' in:(Transcript topView)
    "

    "Created: / 19-07-2019 / 11:36:21 / Claus Gittinger"
    "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 isEmpty ifTrue:[
            Display allTopViews do:[:eachTopView |
                component := self findComponent:componentName in:eachTopView.
                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"
!

pinPOForPin:pinName ofStep:stepName
    |diagramEditor pinPO|

    (diagramEditor := lastComponent) isScrollWrapper ifTrue:[
        diagramEditor := lastComponent scrolledView
    ].
    pinPO := diagramEditor 
            detectPOForWhich:[:po |
                po isPin 
                and:[ po name = pinName
                and:[ po owningStepPO name = stepName ]]
            ].
    ^ pinPO
!

screenBoundsOfComponent:aWidgetOrMenuItemOrTab
    |itemLayout|

    aWidgetOrMenuItemOrTab isView ifTrue:[
        ^ aWidgetOrMenuItemOrTab screenBounds
    ].
    (aWidgetOrMenuItemOrTab askFor:#isMenuItem) ifTrue:[
        |menuPanel menuBounds|

        aWidgetOrMenuItemOrTab isVisible ifTrue:[
            (itemLayout := aWidgetOrMenuItemOrTab layout) notNil ifTrue:[
                menuPanel := aWidgetOrMenuItemOrTab view.
                menuPanel shown ifTrue:[
                    menuBounds := menuPanel screenBounds.
                    ^ itemLayout + menuBounds origin 
                ].
            ].
        ].
    ].
    (aWidgetOrMenuItemOrTab askFor:#isNoteBookTab) ifTrue:[
        (itemLayout := aWidgetOrMenuItemOrTab layout) notNil ifTrue:[
            |notebook notebookBounds|

            notebook := aWidgetOrMenuItemOrTab view.
            notebook shown ifTrue:[
                notebookBounds := notebook screenBounds.
                ^ itemLayout + notebookBounds origin 
            ].
        ]. 
        self halt.
    ].
    ^ nil.
! !

!ShowMeHowItWorks methodsFor:'helpers - mouse buttons'!

click:buttonNr inComponent:component
    "press-release in a component"

    |t|

    t := self shortClickTime.
    ((component isKindOf:Button) 
      or:[(component askFor:#isMenuItem)]
    )ifTrue:[
        t := self longClickTime
    ].    
    self click:buttonNr inComponent:component clickTime:t

    "Created: / 19-07-2019 / 13:18:27 / Claus Gittinger"
    "Modified: / 19-07-2019 / 15:22:47 / Claus Gittinger"
!

click:buttonNr inComponent:viewOrMenuItem clickTime:clickTime 
    "press-release in a component"

    self withViewAndPositionFor:viewOrMenuItem do:[:viewToClick :clickPos |
        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;
                    yourself.
                Delay waitForSeconds:clickTime.
                OperatingSystem 
                    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;
                    yourself.
                ^ 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.
        ].
    ].
!

press:buttonNr inComponent:viewOrMenuItem 
    "press in a component"

    self withViewAndPositionFor:viewOrMenuItem do:[:viewToClick :clickPos |
        viewToClick simulateButtonPress:buttonNr at:clickPos sendDisplayEvent:false.
    ]
!

release:buttonNr inComponent:viewOrMenuItem 
    "release in a component"

    self withViewAndPositionFor:viewOrMenuItem do:[:viewToClick :clickPos |
        viewToClick simulateButtonRelease:buttonNr at:clickPos sendDisplayEvent:false.
    ]
!

withViewAndPositionFor:viewOrMenuItem do:aBlock
    "helper for click, press and release"

    |viewToClick clickPos|

    self assert:viewOrMenuItem notNil.
    verifying ifTrue:[^ self].

    (viewOrMenuItem isKindOf:ItemInView) ifTrue:[
        viewToClick := viewOrMenuItem view.
        clickPos := viewOrMenuItem layout center.
        self assert:(clickPos notNil).
    ] ifFalse:[
        viewToClick := viewOrMenuItem.
        clickPos := viewToClick extent // 2.
    ].
    aBlock value:viewToClick value:clickPos.
! !

!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 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:aWidgetOrMenuItemOrTab speed:pixelsPerSecond
    "move the mouse to aWidget's center"

    |bounds position|
    
    bounds := self screenBoundsOfComponent:aWidgetOrMenuItemOrTab.
    bounds isNil ifTrue:[
        self error:('no bounds found for: %1 (%2)' bindWith:lastComponentName with:aWidgetOrMenuItemOrTab 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.
    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
    ].
    
    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"
!

where:where inComponent:aComponent
    where == #center ifTrue:[
        ^ aComponent center
    ].
    where == #leftCenter ifTrue:[
        ^ aComponent leftCenter + (5@0). 
    ].
    where == #rightCenter ifTrue:[
        ^ aComponent rightCenter - (5@0). 
    ].
    where == #topLeft ifTrue:[
        ^ aComponent topLeft + (5@5). 
    ].
    where == #topCenter ifTrue:[
        ^ aComponent topCenter + (0@5). 
    ].
    where == #topRight ifTrue:[
        ^ aComponent topRight - (5@5). 
    ].
    where == #bottomCenter ifTrue:[
        ^ aComponent bottomCenter - (0 @ 5)
    ].
    where == #bottomLeft ifTrue:[
        ^ aComponent bottomLeft + (5 @ -5)
    ].
    where == #bottomRight ifTrue:[
        ^ aComponent bottomLeft - (5 @ 5)
    ].
    self error:'where is this'
! !

!ShowMeHowItWorks methodsFor:'running'!

do:specArray
    "must run as a separate process;
     otherwise - if started by the app itself -
     no events will be processed while running"

    "/ StartLabel := nil.
    "/ StartLabel := 'start'.
    self do:specArray from:StartLabel


    "
     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"
!

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 -
     no events will be processed while running"

    withUIBoolean ifTrue:[
        ui := ShowMeHowItWorksRunner openOn:self.
    ].
    self do:specArray from:nil

    "
     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"
!

doShowFile:aFilename
    "the file from which the show was loaded"

    |spec|

    theShowFile := aFilename.
    aFilename readingFileWithEncoding:#utf8 do:[:s |
        spec := Array readFrom:s.
    ].
    self do:spec.


!

prepare
    language isNil ifTrue:[
        self setLanguage:(Smalltalk language).
    ].
    translate := false.
    
    application isNil ifTrue:[
        application := WindowGroup activeMainApplication.
    ].
    closeApplicationWhenFinished := false.
    defaultComponentWaitTime isNil ifTrue:[ defaultComponentWaitTime := 1 ].

    "Created: / 23-07-2019 / 10:24:53 / Claus Gittinger"
    "Modified: / 25-07-2019 / 11:48:53 / Claus Gittinger"
!

verify:specArray
    "/ run in verifying mode
    verifying := true.
    [
        Error handle:[:ex |
            Transcript showCR:('Possible error (encountered while verifying):\.... %1' withCRs)
                         with:ex description withCRs.
            Display ctrlDown ifTrue:[ex reject].
        ] do:[
            self show:' '. "/ to avoid an audible disturbance on OSX
            self doStream:(specArray readStream)
        ].
    ] ensure:[
        verifying := false.
    ].
! !

!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"

    self prepare.
    "/ run once in verifying mode
    self verify:specArray.

    [
        |wasFlyByActive|

        wasFlyByActive := FlyByHelp isSuspended.
        FlyByHelp suspend.

        [
            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:[
            wasFlyByActive ifTrue:[FlyByHelp resume].
        ].
    ] 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"
    
    |numArgs sel args method|

    op isArray ifTrue:[
        op first isArray ifTrue:[
            self doStream:op readStream.
            ^ self.
        ].
        
        "/ 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].
    
    lastResult := self perform:sel withArguments:args.
    ^ lastResult
    
"<<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 / 15:34:55 / Claus Gittinger"
    "Modified: / 23-07-2019 / 09:14:26 / Claus Gittinger"
!

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.

    streamStack isNil ifTrue:[ streamStack := OrderedCollection new ].
    streamStack add:opStream.

    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:[
            nextCommand := opStream next.
            self doCommand:nextCommand.

            Display shiftDown ifTrue:[
                (IntroShownCount ? 0) > 3 ifFalse:[
                    self tell:(self translate:'You pressed the SHIFT key.').
                ].    
                self tell:(self translate:'Do you want to stop the show?').
                (Dialog confirm:(resources stringWithCRs:'Stop the demonstration?'))
                ifTrue:[
                    self tell:(self translate:'OK,').
                    self tell:(self translate:(self randomThankYou)).
                    ^ AbortOperationRequest raise
                ].    
            ].    
        ].    
    ] ensure:[
        streamStack removeLast.
        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: / 23-07-2019 / 11:48:45 / Claus Gittinger"
!

translate:aString
    ^ self class classResources stringWithCRs:aString.
! !

!ShowMeHowItWorks::ItemInView 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:]
        exept MBP

    [instance variables:]

    [class variables:]

    [see also:]

"
! !

!ShowMeHowItWorks::ItemInView methodsFor:'accessing'!

boundsInView
    ^ boundsInView
!

item
    ^ item
!

itemValue
    ^ item itemValue
!

layout
    ^ item layout
!

view
    ^ view
!

view:viewArg item:itemArg boundsInView:boundsInViewArg 
    view := viewArg.
    item := itemArg.
    boundsInView := boundsInViewArg.
! !

!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'!

version_CVS
    ^ '$Header$'
! !