ActiveHelp.st
author Claus Gittinger <cg@exept.de>
Sun, 29 Oct 1995 20:36:22 +0100
changeset 109 9e1383121df4
parent 101 44a6c9a92c00
child 114 e577a2f332d0
permissions -rw-r--r--
*** empty log message ***

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


'From Smalltalk/X, Version:2.10.5 on 26-mar-1995 at 10:19:20 am'!

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

!ActiveHelp class methodsFor:'documentation'!

version
"
$Header: /cvs/stx/stx/libview2/ActiveHelp.st,v 1.6 1995-09-15 23:50:04 claus Exp $
"
!

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, NewLauncher 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.
"
!

examples
"
    (make certain that activeHelp is turned on ...)

    the following example uses a Plug as a model replacement.
    In concrete application, you would create a method to implement the helpText
    query message.

	|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

    alternative, display of the helpMessage in a local, private view:

	|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

"
!

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

! !

!ActiveHelp class methodsFor:'initialization'!

initialize
    ShowTime := 15.
    DelayTime := 2.

    "
     ActiveHelp initialize
    "
! !

!ActiveHelp class methodsFor:'times'!

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

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

!ActiveHelp class methodsFor:'startup'!

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

    "
     ActiveHelp start
    "
!

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

    "
     ActiveHelp stop
    "
!

isActive 
    ^ TheOneAndOnlyHelpListener notNil
! !

!ActiveHelp methodsFor:'private'!

helpTextFor:aView atX:x y:y
    "pointer entered aView; 
     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 walk and the
     returned string is returned."

    |model text view org found 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 superView) 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"
!

initiateHelpFor:aView atX:x y:y
    |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 forSeconds:DelayTime) wait.
		    showProcess := nil.
		    self showHelp:text for:aView
	    ] forkAt:(Processor userSchedulingPriority + 1).
	] ifFalse:[
	    self showHelp:text for:aView
	]
    ].
!

hideIfPointerLeft:aView
    |whereOnScreen p|

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

    whereOnScreen := aView device pointerPosition.

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

!ActiveHelp methodsFor:'listening'!

buttonPress:state x:x y:y view:view
    self hideHelp.
    ^ false
!

pointerEnter:state x:x y:y view:aView
    |text 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
!

pointerLeave:state view:view
    self hideIfPointerLeft:view.
    ^ false
!

keyPress:state x:x y:y view:view
    self hideHelp.
    ^ false
!

buttonMotion:state x:x y:y view:view
    self hideIfPointerLeft:view.
    ^ false
! !

!ActiveHelp methodsFor:'show / hide help'!

hideHelp
    |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.
    ]
!

showHelp:aHelpText for:view
    |org p v|

    view == currentView 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.

    org := view device pointerPosition.
    org := org + (10@10).
    (org x + v width) > view device width ifTrue:[
	org := (org x - v width) @ org y
    ].
    (org y + v height) > view device 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 := [
	Process terminateSignal handle:[:ex |
	    closeProcess := nil.
	] do:[
	    (Delay forSeconds:ShowTime) wait.
	    [
		currentHelpView notNil ifTrue:[
		    currentHelpView destroy.
		    currentHelpView := nil.
		]
	    ] valueUninterruptably
	].
    ] forkAt:(Processor userSchedulingPriority + 1).

    "Modified: 31.8.1995 / 19:20:45 / claus"
! !

ActiveHelp initialize!