ActiveHelp.st
author ca
Sun, 15 Jun 1997 12:15:40 +0200
changeset 596 a184f2e4dbbb
parent 344 42ae71c13eef
child 602 b1ac53a44327
permissions -rw-r--r--
give the views application a chance to decide where to show the help text (i.e. in its own information area)

"
 COPYRIGHT (c) 1995 by Claus Gittinger
	      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.
"


EventListener subclass:#ActiveHelp
	instanceVariableNames:'currentView currentHelpView currentFrame showProcess closeProcess'
	classVariableNames:'DelayTime ShowTime TheOneAndOnlyHelpListener'
	poolDictionaries:''
	category:'Interface-Help'
!

!ActiveHelp class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1995 by Claus Gittinger
	      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
"
    The active help listener.
    The one and only instance of myself intercepts incoming mouse & keyboard 
    events for the display device, being especially interested in view-enter/
    leave enents. When such an event arrives, it asks the corresponding view
    or its model for a help message and display it via an ActiveHelpView.
    This query is repeated along the views superView chain, until any model or
    view returns a nonNil answer for the #helpTextFor:at or #helpTextFor
    message.

    All I need for automatic help is some model/view/applicationModel along
    the superview chain of the entered component, which responds to the
    #helpTextFor: message with a non-nil (string-) answer.
    I close down the help view after a while, if a key is pressed or the mouse
    moved to another view.

    Who should provide the helpText:

        the best place is the application object (an instance of ApplicationModel)
        or the topView, if its a derived class of StandardSystemView.
        This should know about its components and return the string
        when asked via #helpTextFor:aComponent.
        See examples in FileBrowser, Launcher etc.

    Be aware, that for applicationModels, there must be a link from the
    topView to this applicationModel 
    (set via: aTopView application:anApplicationModel)
    otherwise, the helpManager has no means of finding the application which
    corresponds to a view.

    Who should display the helpText:

        by default, the helpListener opens a little popup view, which displays the
        returned help message. However, a nice trick which can be used by applications
        is to create an infoLabel as a subview of the topFrame (a la windows)
        and display the text right in the #helpTextFor: method. To cheat the
        help listener, this method should then return nil, to keep it silent.


    [author:]
        Claus Gittinger

    [start with:]
        ActiveHelp start
        ActiveHelp stop

    [See also:]
        ActiveHelpView
        WindowGroup WindowEvent
        ApplicationModel StandardSystemView
"
!

examples
"
    (make certain that activeHelp is turned on ...
     ... otherwise, you will see nothing)

    the following example uses a Plug as a model replacement.
    In concrete application, you would create a method to implement the helpText
    query message.
                                                                        [exBegin]
        |app top button1 button2|

        app := Plug new.
        app respondTo:#helpTextFor:
                 with:[:view | 
                               view == button1 ifTrue:[
                                 'this is button1'
                               ] ifFalse:[
                                 view == button2 ifTrue:[
                                   'some help for button2'
                                 ] ifFalse:[
                                   nil
                                 ]
                               ]
                      ].

        top := StandardSystemView new.
        top extent:300@100.
        button1 := Button label:'b1' in:top.
        button1 origin:0.0@0.0 corner:0.5@30. 
        button2 := Button label:'b2' in:top.
        button2 origin:0.5@0.0 corner:1.0@30.
        top model:app. '<-- normally this would be: top application:app'.
        top open
                                                                        [exEnd]

    (make certain that activeHelp is turned on ...
     ... otherwise, you will see nothing)

    alternatively, display of the helpMessage in a local, private view:
                                                                        [exBegin]
        |app top button1 button2 infoView|

        app := Plug new.
        app respondTo:#helpTextFor:
                 with:[:view | infoView label:'info ...'.
                               view == button1 ifTrue:[
                                 infoView label:'this is button1'
                               ].
                               view == button2 ifTrue:[
                                 infoView label:'some help for button2'
                               ].
                               nil
                      ].

        top := StandardSystemView new.
        top extent:300@100.
        button1 := Button label:'b1' in:top.
        button1 origin:0.0@0.0 corner:0.5@30. 
        button2 := Button label:'b2' in:top.
        button2 origin:0.5@0.0 corner:1.0@30.
        infoView := Label label:'info ...' in:top.
        infoView level:-1; origin:0.0@1.0 corner:1.0@1.0.
        infoView topInset:(infoView preferredExtent y negated - 3);
                 leftInset:3; 
                 rightInset:3; 
                 bottomInset:3;
                 adjust:#left.
        top model:app. '<-- normally this would be: top application:app'.
        top open
                                                                        [exEnd]
"
! !

!ActiveHelp class methodsFor:'initialization'!

initialize
    "set default delay & help-display times"

    ShowTime := 15.
    DelayTime := 2.

    "
     ActiveHelp initialize
    "

    "Modified: 27.4.1996 / 15:07:27 / cg"
! !

!ActiveHelp class methodsFor:'startup'!

isActive
    "return true, if activeHelp is turned on"

    ^ TheOneAndOnlyHelpListener notNil

    "Modified: 27.4.1996 / 15:07:57 / cg"
!

start
    "start activeHelp"

    TheOneAndOnlyHelpListener isNil ifTrue:[
        TheOneAndOnlyHelpListener := self new.
    ].
    TheOneAndOnlyHelpListener listen

    "
     ActiveHelp start
    "

    "Modified: 27.4.1996 / 15:08:05 / cg"
!

stop
    "stop activeHelp"

    TheOneAndOnlyHelpListener notNil ifTrue:[
        TheOneAndOnlyHelpListener unlisten.
    ].
    TheOneAndOnlyHelpListener := nil

    "
     ActiveHelp stop
    "

    "Modified: 27.4.1996 / 15:08:11 / cg"
! !

!ActiveHelp class methodsFor:'times'!

delayTime:numberOfSeconds
    "set the delay (the time, the cursor has to be in the view
     before help is shown). The default is 2 seconds."

    DelayTime := numberOfSeconds

    "
     ActiveHelp delayTime:0.5
     ActiveHelp delayTime:2
     ActiveHelp delayTime:10
    "
!

showTime:numberOfSeconds
    "set the number of seconds, a help messages is to be shown.
     The default is 15 seconds."

    ShowTime := numberOfSeconds

    "
     ActiveHelp showTime:10
     ActiveHelp showTime:99999 
     ActiveHelp showTime:30
    "
! !

!ActiveHelp methodsFor:'listening'!

buttonMotion:state x:x y:y view:view
    "handle motion events - if the mousepointer left the 
     previous helped view, hide the help"

    self hideIfPointerLeft:view.
    ^ false

    "Modified: 27.4.1996 / 15:09:48 / cg"
!

buttonPress:state x:x y:y view:view
    "handle button press - unconditionally hide the help"

    self hideHelp.
    ^ false

    "Modified: 27.4.1996 / 15:09:45 / cg"
!

keyPress:state x:x y:y view:view
    "handle key press - unconditionally hide the help"

    self hideHelp.
    ^ false

    "Modified: 27.4.1996 / 15:09:57 / cg"
!

pointerEnter:state x:x y:y view:aView
    "handle pointer entering a view; setup timeOut to show help"

    |p|

    showProcess notNil ifTrue:[
        p := showProcess. showProcess := nil.
        p terminate.
    ].
    self hideIfPointerLeft:aView.
    aView topView == currentHelpView ifTrue:[
        ^ true
    ].

    self initiateHelpFor:aView atX:x y:y.
    ^ false

    "Modified: 27.4.1996 / 15:10:27 / cg"
!

pointerLeave:state view:view
    "handle pointer leaving a view; hide help text"

    self hideIfPointerLeft:view.
    ^ false

    "Modified: 27.4.1996 / 15:10:41 / cg"
! !

!ActiveHelp methodsFor:'private'!

helpTextFor:aView atX:x y:y
    "retrieve helptext for aView as a string; 
     walk along the views superView chain,
     asking models and views encountered while walking.
     The first one who understands and returns a nonNil answer to the
     #helpTextFor:at: or #helpTextFor: message ends this search and the
     returned string is returned."

    |model text view v sv|

    view := aView.
    (model := aView model) notNil ifTrue:[
        (model respondsTo:#helpTextFor:at:) ifTrue:[
            text := model helpTextFor:aView at:x@y.
            text notNil ifTrue:[^ text].
        ].
        (model respondsTo:#helpTextFor:) ifTrue:[
            text := model helpTextFor:aView.
            text notNil ifTrue:[^ text].
        ]
    ].

    (aView respondsTo:#helpText) ifTrue:[
        text := aView helpText.
        text notNil ifTrue:[^ text].
    ].

    "walk up the chain - maybe someone knows about its subview ..."
    v := aView.

    [(sv := v container) notNil] whileTrue:[
        (model := sv model) notNil ifTrue:[
            (model respondsTo:#helpTextFor:at:) ifTrue:[
                text := model helpTextFor:aView at:x@y.
                text notNil ifTrue:[^ text].
            ].
            (model respondsTo:#helpTextFor:) ifTrue:[
                text := model helpTextFor:aView.
                text notNil ifTrue:[^ text].
            ]
        ].

        (sv respondsTo:#helpTextFor:) ifTrue:[
            text := sv helpTextFor:aView.
            text notNil ifTrue:[^ text].
            text := sv helpTextFor:v.
            text notNil ifTrue:[^ text].
         ].
         v := sv.
    ].

    (v notNil and:[v respondsTo:#application]) ifTrue:[
        (model := v application) notNil ifTrue:[
            (model respondsTo:#helpTextFor:at:) ifTrue:[
                text := model helpTextFor:aView at:x@y.
                text notNil ifTrue:[^ text].
            ].
            (model respondsTo:#helpTextFor:) ifTrue:[
                text := model helpTextFor:aView.
                text notNil ifTrue:[^ text].
            ]
        ]
    ].
    (v notNil and:[v respondsTo:#model]) ifTrue:[
        (model := v model) notNil ifTrue:[
            (model respondsTo:#helpTextFor:at:) ifTrue:[
                text := model helpTextFor:aView at:x@y.
                text notNil ifTrue:[^ text].
            ].
            (model respondsTo:#helpTextFor:) ifTrue:[
                text := model helpTextFor:aView.
                text notNil ifTrue:[^ text].
            ]
        ]
    ].

    (view class respondsTo:#helpText) ifTrue:[
        text := view class helpText.
        text notNil ifTrue:[^ text].
    ].

    ^ nil

    "Modified: 31.8.1995 / 20:38:00 / claus"
    "Modified: 5.6.1996 / 14:18:23 / cg"
!

hideIfPointerLeft:aView
    "hide help, if the pointer is not in aView"

    |whereOnScreen p|

"/    showProcess notNil ifTrue:[
"/        p := showProcess. showProcess := nil.
"/        p terminate.
"/    ].

    whereOnScreen := aView graphicsDevice pointerPosition.

    (currentFrame notNil
    and:[(currentFrame insetBy:1@1) containsPoint:whereOnScreen]) ifFalse:[
        self hideHelp.
        currentView := nil
    ].

    "Modified: 28.5.1996 / 20:18:28 / cg"
!

initiateHelpFor:aView atX:x y:y
    "start a timeout process to display help for aView after some delay"

    |text p|

    text := self helpTextFor:aView atX:x y:y.

    text notNil ifTrue:[
        DelayTime > 0 ifTrue:[
            showProcess notNil ifTrue:[
                p := showProcess. showProcess := nil.
                p terminate.
            ].
            showProcess := [
                    Delay waitForSeconds:DelayTime.
                    showProcess := nil.
                    self showHelp:text for:aView
            ] forkAt:(Processor userSchedulingPriority + 1).
        ] ifFalse:[
            self showHelp:text for:aView
        ]
    ].

    "Modified: 7.6.1996 / 10:02:19 / cg"
! !

!ActiveHelp methodsFor:'show / hide help'!

hideHelp
    "hide the help text"

    |p|

    showProcess notNil ifTrue:[
        p := showProcess. showProcess := nil.
        p terminate.
    ].
    currentHelpView notNil ifTrue:[
        [
            currentHelpView destroy.
            currentHelpView := nil.
            currentView := nil.
        ] valueUninterruptably
    ].
    currentFrame := nil.
    closeProcess notNil ifTrue:[
        p := closeProcess. closeProcess := nil.
        p terminate.
    ]

    "Modified: 27.4.1996 / 15:12:13 / cg"
!

showHelp:aHelpText for:view
    "show the help text for aView"

    |org p v dev top app|

    view == currentView ifTrue:[^ self].

    "/ give the views application a chance
    "/ to decide where to show the help text
    "/ (i.e. in its own information area)

    top := view topView.
    (app := top application) notNil ifTrue:[
        (app showHelp:aHelpText for:view) ifTrue:[
            ^ self
        ]
    ].

    closeProcess notNil ifTrue:[
        p := closeProcess. closeProcess := nil.
        p terminate.
    ].
    currentHelpView notNil ifTrue:[
        self hideHelp
    ].

    org := view originRelativeTo:nil.
    currentFrame := org extent:view extent.
    org :=org + (view extent // 2).

    v := ActiveHelpView for:aHelpText withCRs.

    dev := view graphicsDevice.
    org := dev pointerPosition.
    org := org + (10@10).
    (org x + v width) > dev width ifTrue:[
        org := (org x - v width) @ org y
    ].
    (org y + v height) > dev height ifTrue:[
        org := org x @ (org y - v height).
    ].

    v origin:org.
"/    currentHelpView open.
    v realize.
    v enableButtonMotionEvents.
    v enableMotionEvents.
    currentHelpView := v.

    currentView := view.
    closeProcess := [
        [
            (Delay forSeconds:ShowTime) wait.
            [
                currentHelpView notNil ifTrue:[
                    currentHelpView destroy.
                    currentHelpView := nil.
                ]
            ] valueUninterruptably
        ] valueOnUnwindDo:[
            closeProcess := nil.
        ].
    ] forkAt:(Processor userSchedulingPriority + 1).

    "Modified: 31.8.1995 / 19:20:45 / claus"
    "Modified: 16.10.1996 / 00:36:22 / cg"
! !

!ActiveHelp class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libview2/ActiveHelp.st,v 1.19 1997-06-15 10:15:40 ca Exp $'
! !
ActiveHelp initialize!