ButtonController.st
author Claus Gittinger <cg@exept.de>
Tue, 27 Feb 1996 14:51:16 +0100
changeset 417 3f48ca2beb7d
parent 349 e4382398fc59
child 582 e151eeae2c9a
permissions -rw-r--r--
use device shiftDown - NOT sensor shiftDown

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

Controller subclass:#ButtonController
	instanceVariableNames:'enableChannel pressChannel releaseChannel pressed active entered
		isTriggerOnDown autoRepeat repeatBlock initialDelay repeatDelay
		pressActionBlock releaseActionBlock isToggle'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Support-Controllers'
!

!ButtonController 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
"
    ButtonControllers are used with buttons and handle all user interaction.
    These are automatically created when a Button is created, therefore no manual
    action is required for creation.
    In normal applications, you dont have to care for the controller; 
    access to the controllers behavior is possible via messages to the button.
    (setting actions, controlling autorepeat etc.)

    See examples in the Button class.

    Instance variables:

      enableChannel           <ValueHolder    pressing is allowed (default: true)
			       on Boolean>    

      pressed                 <Boolean>       true if currently pressed (read-only)

      entered                 <Boolean>       true if the cursor is currently in this view

      isTriggerOnDown         <Boolean>       controls if the action should be executed on
					      press or on release (default: on release).

      isToggle                <Boolean>       controls if the button should show toggle
					      behavior (as opposed to one-shot behavior)

      pressActionBlock        <Block>         block to evaluate when pressed (default: noop)

      releaseActionBlock      <Block>         block to evaluate when released (default: noop)

      autoRepeat              <Boolean>       auto-repeats when pressed long enough (default: false)

      initialDelay            <Number>        seconds till first auto-repeat (default: 0.2)

      repeatDelay             <Number>        seconds of repeat intervall (default: 0.025)

      repeatBlock             <Block>         block evaluated for auto-repeat (internal)

      active                  <Boolean>       true during action evaluation (internal)
"
! !

!ButtonController class methodsFor:'defaults'!

defaultInitialDelay
    "when autorepeat is enabled, and button is not released,
     start repeating after initialDelay seconds"

    ^ 0.2
!

defaultRepeatDelay
    "when autorepeat is enabled, and button is not released,
     repeat every repeatDelay seconds"

    ^ 0.025
! !

!ButtonController methodsFor:'accessing-behavior'!

action:aBlock
    "convenient method: depending on the setting the triggerOnDown flag,
     either set the press-action & clear any release-action or
     vice versa, set the release-action and clear the press-action."

    isTriggerOnDown ifTrue:[
        releaseActionBlock := nil.
        pressActionBlock := aBlock
    ] ifFalse:[
        releaseActionBlock := aBlock.
        pressActionBlock := nil
    ]

    "Modified: 9.2.1996 / 22:41:22 / cg"
!

autoRepeat
    "turn on autorepeat. OBSOLETE; use #autoRepeat:"

    self autoRepeat:true.

    "Modified: 9.2.1996 / 22:42:46 / cg"
!

autoRepeat:aBoolean
    "turn on/off autorepeat"

    autoRepeat := aBoolean.
    repeatBlock := [self repeat]

    "Modified: 5.9.1995 / 22:06:00 / claus"
!

beToggle
    "make the receiver act like a toggle"

    isTriggerOnDown := true.
    isToggle := true
!

beTriggerOnDown
    "make the receiver act on button press"

    isTriggerOnDown := true
!

beTriggerOnUp
    "make the receiver act on button release"

    isTriggerOnDown := false
!

disable
    "disable the button"

    enableChannel value ifTrue:[
	enableChannel value:false.
	"/ view redraw    - not needed; I listen to enableChannel
    ]
!

enable
    "enable the button"

    enableChannel value ifFalse:[
	enableChannel value:true.
	"/ view redraw    - not needed; I listen to enableChannel
    ]
!

isTriggerOnDown
    "return true, if I trigger on press
     (in contrast to triggering on up, which is the default)"

    ^ isTriggerOnDown
!

pressAction
    "return the pressAction; thats the block which gets evaluated
     when the button is pressed (if non-nil)"

    ^ pressActionBlock
!

pressAction:aBlock
    "define the action to be performed on press"

    pressActionBlock := aBlock
!

releaseAction
    "return the releaseAction; thats the block which gets evaluated
     when the button is relreased (if non-nil)"

    ^ releaseActionBlock
!

releaseAction:aBlock
    "define the action to be performed on release"

    releaseActionBlock := aBlock
!

triggerOnDown:aBoolean
    "set/clear the flag which controls if the action block is to be evaluated
     on press or on release. 
     (see also ST-80 compatibility methods beTriggerOn*)"

    isTriggerOnDown := aBoolean
! !

!ButtonController methodsFor:'accessing-channels'!

enableChannel
    "return the valueHolder which holdes the enable
     boolean value"

    ^ enableChannel
!

enableChannel:aValueHolder
    "set the valueHolder, which holds the enable boolean value"

    |wasEnabled|

    enableChannel notNil ifTrue:[
	wasEnabled := enableChannel value.
	enableChannel retractInterestsFor:self. 
    ] ifFalse:[
	wasEnabled := true
    ].
    enableChannel := aValueHolder.
    aValueHolder onChangeSend:#enableStateChange to:self.
    enableChannel value ~~ wasEnabled ifTrue:[
	self enableStateChange
    ]

    "Modified: 17.9.1995 / 19:41:18 / claus"
!

pressChannel:aChannel
    pressChannel := aChannel
!

releaseChannel:aChannel
    releaseChannel := aChannel
! !

!ButtonController methodsFor:'accessing-state'!

active
    "return true, if I am active; 
     that is: currently performing my action.
     This query can be used to avoid multiple redraws."

    ^ active
!

active:aBoolean
    active := aBoolean
!

enabled
    "return true, if I am enabled"

    ^ enableChannel value
!

entered
    "return true, if the mouse pointer is currently in my view"

    ^ entered
!

entered:aBoolean
    entered := aBoolean
!

pressed
    "return true, if I am pressed"

    ^ pressed
!

pressed:aBoolean
    pressed ~~ aBoolean ifTrue:[
	pressed := aBoolean.
	self performAction.
    ].
!

setPressed:aBoolean
    pressed := aBoolean.

    "Created: 14.11.1995 / 21:37:08 / cg"
!

toggle
    "toggle and perform the action"

    enableChannel value ifTrue:[
	self toggleNoAction.
	self performAction.
	view changed:#toggle with:pressed
    ]
!

toggleNoAction
    "toggle, but do NOT perform any action"

    pressed ifTrue:[
	view turnOff.
	pressed := false.
    ] ifFalse:[
	view turnOn.
	pressed := true.
    ].
! !

!ButtonController methodsFor:'event handling'!

buttonMultiPress:button x:x y:y
    ^ self buttonPress:button x:x y:y
!

buttonPress:button x:x y:y
    (button == 1 or:[button == #select]) ifFalse:[
	^ super buttonPress:button x:x y:y
    ].

    enableChannel value ifTrue:[
	isToggle ifTrue:[
	    self toggle.
	    ^ self
	].

	pressed ifFalse:[
	    pressed := true.
	    view showActive.

	    (pressActionBlock notNil or:[model notNil]) ifTrue:[
		"
		 force output - so that button is drawn correctly in case
		 of any long-computation (at high priority)
		"
		view flush.
	    ].

	    self performAction.

	    autoRepeat ifTrue:[
		Processor addTimedBlock:repeatBlock afterSeconds:initialDelay
	    ]
	]
    ]

    "Modified: 16.12.1995 / 17:31:55 / cg"
!

buttonRelease:button x:x y:y
    "button was released - if enabled, perform releaseaction"

    (button == 1 or:[button == #select]) ifFalse:[
	^ super buttonRelease:button x:x y:y
    ].

    isToggle ifTrue:[
	^ self
    ].

    pressed ifTrue:[
	autoRepeat ifTrue:[
	    Processor removeTimedBlock:repeatBlock
	].
	pressed := false.
	view showPassive.

	enableChannel value ifTrue:[
	    "
	     only perform action if released within myself
	    "
	    ((x >= 0) 
	    and:[x <= view width
	    and:[y >= 0
	    and:[y <= view height]]]) ifTrue:[
		(releaseActionBlock notNil or:[model notNil]) ifTrue:[
		    "
		     force output - so that button is drawn correctly in case
		     of any long-computation (at high priority)
		    "
		    view flush.
		].

		self performAction.
	    ]
	]
    ]

    "Modified: 16.12.1995 / 17:32:09 / cg"
!

enableStateChange
    "this is sent, whenever the enable value has changed"

    view notNil ifTrue:[view enableStateChangeRedraw]

    "Modified: 17.9.1995 / 19:55:52 / claus"
!

keyPress:key x:x y:y
    "trigger on Return and space, if I am the focusView of my group
     (i.e. if I got an explicit focus)"

    key == Character space ifTrue:[
        view hasFocus ifTrue:[
            "just simulate a buttonPress/release here."
            self buttonPress:1 x:0 y:0.
            self buttonRelease:1 x:0 y:0.
            ^ self.
        ]
    ].
    view keyPress:key x:x y:y

    "Modified: 9.2.1996 / 23:11:58 / cg"
!

performAction
    |action value|

    isToggle ifTrue:[
	value := pressed
    ] ifFalse:[
	value := true
    ].

    "
     ST/X style actionBlock evaluation & channel notification ...
    "
    pressed ifTrue:[
	action := pressActionBlock.
    ] ifFalse:[
	action := releaseActionBlock.
    ].
    action notNil ifTrue:[
	active := true.
	action numArgs == 0 ifTrue:[
	    action value
	] ifFalse:[
	    action value:value
	].
	active := false.
    ].
    pressChannel notNil ifTrue:[
	pressChannel value:pressed 
    ].
    releaseChannel notNil ifTrue:[
	releaseChannel value:pressed not
    ].

    "
     ST-80 style model notification ...
    "
    (isToggle
    or:[(isTriggerOnDown and:[pressed])
    or:[isTriggerOnDown not and:[pressed not]]]) ifTrue:[
	"the ST-80 way of doing things"
	view notNil ifTrue:[
	    active := true.
	    view sendChangeMessageWith:value.
	    active := false.
	].
    ].
!

pointerEnter:state x:x y:y
    "mouse pointer entered my view.
     Redraw with enteredColors if they differ from the normal colors"

    entered := true.
    enableChannel value ifTrue:[
	pressed ifTrue:[
	    "
	     reentered after a leave with mouse-button down;
	     restart autorepeating and/or if I am a button with
	     triggerOnDown, show active again.
	    "
	    autoRepeat ifTrue:[
		Processor addTimedBlock:repeatBlock afterSeconds:initialDelay
	    ].
	    isTriggerOnDown ifFalse:[
		view showActive.
	    ]
	] ifFalse:[
	    view redraw
	]
    ]
!

pointerLeave:state
    "mouse pointer left my view.
     Redraw with normal colors if they differ from enteredColors"

    entered := false.
    pressed ifTrue:[
	"
	 leave with mouse-button down;
	 stop autorepeating and/or if I am a button with
	 action on release, show passive
	"
	autoRepeat ifTrue:[
	    Processor removeTimedBlock:repeatBlock
	].
	isTriggerOnDown ifFalse:[
	    view showPassive.
	]
    ] ifFalse:[
	enableChannel value ifTrue:[
	    view redraw
	]
    ]
!

repeat
    "this is sent from the autorepeat-block, when the button has been pressed long
     enough; it simulates a release-press, by evaluating both release
     and press actions."

    |dly|

    pressed ifTrue:[
        enableChannel value ifTrue:[
            active ifFalse:[
                self performAction.
"/                active := true.
"/                releaseActionBlock notNil ifTrue:[releaseActionBlock value].
"/                pressActionBlock notNil ifTrue:[pressActionBlock value].
"/                active := false.

                autoRepeat ifTrue:[
                    view device shiftDown ifTrue:[
                        dly := repeatDelay / 4.
                    ] ifFalse:[
                        dly := repeatDelay
                    ].
                    Processor addTimedBlock:repeatBlock afterSeconds:dly
                ]
            ]
        ]
    ]

    "Modified: 27.2.1996 / 14:48:13 / cg"
! !

!ButtonController methodsFor:'initialization'!

initialize
    super initialize.

    enableChannel := true asValue.
    enableChannel onChangeSend:#enableStateChange to:self.

    active := false.
    pressed := false.
    entered := false.
    autoRepeat := false.
    initialDelay := self class defaultInitialDelay.
    repeatDelay := self class defaultRepeatDelay.
    isTriggerOnDown := false.
    isToggle := false.
! !

!ButtonController class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libwidg/ButtonController.st,v 1.30 1996-02-27 13:51:16 cg Exp $'
! !