EnterFieldGroup.st
author Claus Gittinger <cg@exept.de>
Mon, 04 Mar 1996 23:32:09 +0100
changeset 466 e878bd3b227e
parent 322 3a25112b58b1
child 581 4d48caecf4a0
permissions -rw-r--r--
delegated keyboardMessages get nil as x/y

"
 COPYRIGHT (c) 1992 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.
"

Object subclass:#EnterFieldGroup
	instanceVariableNames:'fields currentField leaveAction wrap'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Support'
!

!EnterFieldGroup class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1992 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
"
    EnterFieldGroup controls the interaction between EnterFields
    enabling the next/prev field when a field is left. 
    Instances of this class keep track of which field of the group is the 
    currentField (i.e. the one getting keyboard input) and forwards input
    to the active field (having the inputField delegate its input to me).

    The block accessable as leaveAction is evaluated when the last
    field of the group is left (by cursor-down or cr). 
    Usually this block triggers accept on the fields and/or performs some
    followup processing and closes  the topview (for example: in a dialog).

    EnterFieldGroups can be used as a delegate (of the topView) to forward
    input (entered into the topView) to the currently active field.

    Stepping to previous field is via CursorUp/PreviousField,
    to next field via CursorDown/NextField/Tab.
    Notice, that by default, the editField takes the tab-character as
    a normal character. To step using tab, you have to add the Tab key to the
    fields leaveKeys.

    Instance variables:

	fields          <Collection of EditField>       the fields of the group

	currentField    <EditField>                     the active field

	leaveAction     <nil|Block>                     action to perform, when the
							last field is left by a non-wrap

	wrap            <Boolean>                       if true, non-return next-keys wrap
							back to the first field.
							If false (the default), next in
							the last field is taken as return.
							This is ignored, if no leaveAction was 
							defined.
"
!

examples 
"
    without a group - user has to enter mouse into the next field to activate it;
    Cursor-keys dont work:

	|top panel field1 field2 field3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	top open


    with a group - Return-key or CursorKey enables next field:
    (but still, mouse pointer has to be moved into any of the fields,
     because the topView does not forward its input into the fields)

	|top panel group field1 field2 field3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	group := EnterFieldGroup new.
	group add:field1; add:field2; add:field3.

	top open



    same, enables tabbing via the Tab key:

	|top panel group field1 field2 field3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	group := EnterFieldGroup new.
	group add:field1; add:field2; add:field3.

	field1 leaveKeys:(EditField defaultLeaveKeys copyWith:#Tab).
	field2 leaveKeys:(EditField defaultLeaveKeys copyWith:#Tab).
	field3 leaveKeys:(EditField defaultLeaveKeys copyWith:#Tab).
	top open



    with a group - Return-key or CursorKey enables next field:
    input into topView is forwarded to the group:

	|top panel group field1 field2 field3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	group := EnterFieldGroup new.
	group add:field1; add:field2; add:field3.

	top delegate:group.
	top open



    as above, but close the box when the last field is left:

	|top panel group field1 field2 field3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	group := EnterFieldGroup new.
	group add:field1; add:field2; add:field3.
	group leaveAction:[top destroy].

	top delegate:group.
	top open



    same as above, with Tab-key stepping:

	|top panel group field1 field2 field3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	group := EnterFieldGroup new.
	group add:field1; add:field2; add:field3.
	group leaveAction:[top destroy].

	field1 leaveKeys:(EditField defaultLeaveKeys copyWith:#Tab).
	field2 leaveKeys:(EditField defaultLeaveKeys copyWith:#Tab).
	field3 leaveKeys:(EditField defaultLeaveKeys copyWith:#Tab).

	top delegate:group.
	top open



    the next example shows that the input order is defined by the
    order in the group; NOT by the physical layout of the fields in the superview:
    (i.e. you can arrange your fields in multiple framedBoxes, panels or
     subviews - independent of the tab-stepping order)

	|top panel group field1 field2 field3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	group := EnterFieldGroup new.
	group add:field3; add:field2; add:field1.
	group leaveAction:[top destroy].

	top delegate:group.
	top open



    using a single model for all fields:
    (here, we use a Plug to simulate a more complex model):

	|top panel group field1 field2 field3 model
	 value1 value2 value3|

	top := StandardSystemView new.
	top extent:200@200.

	panel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.

	panel add:(field1 := EditField extent:(1.0 @ nil)).
	panel add:(field2 := EditField extent:(1.0 @ nil)).
	panel add:(field3 := EditField extent:(1.0 @ nil)).

	group := EnterFieldGroup new.
	group add:field1; add:field2; add:field3.
	group leaveAction:[top destroy].

	value1 := 'one'. value2 := 'two'. value3 := 'three'.

	model := Plug new.
	model respondTo:#value1 with:[value1].
	model respondTo:#value1: with:[:arg | value1 := arg].
	model respondTo:#value2 with:[value2].
	model respondTo:#value2: with:[:arg | value2 := arg].
	model respondTo:#value3 with:[value3].
	model respondTo:#value3: with:[:arg | value3 := arg].

	field1 model:model; aspect:#value1; change:#value1:.
	field2 model:model; aspect:#value2; change:#value2:.
	field3 model:model; aspect:#value3; change:#value3:.

	top delegate:group.
	top openModal.

	Transcript showCr:'value1: ' , value1.
	Transcript showCr:'value2: ' , value2.
	Transcript showCr:'value3: ' , value3.


    the above is done automatically for you, if you add inputFields
    to a dialogBox:

	|box model
	 value1 value2 value3|

	box := DialogBox new.
	box extent:200@200.

	value1 := 'one'. value2 := 'two'. value3 := 'three'.

	model := Plug new.
	model respondTo:#value1 with:[value1].
	model respondTo:#value1: with:[:arg | value1 := arg].
	model respondTo:#value2 with:[value2].
	model respondTo:#value2: with:[:arg | value2 := arg].
	model respondTo:#value3 with:[value3].
	model respondTo:#value3: with:[:arg | value3 := arg].

	(box addInputFieldOn:model) aspect:#value1; change:#value1:.
	box addVerticalSpace.
	(box addInputFieldOn:model) aspect:#value2; change:#value2:.
	box addVerticalSpace.
	(box addInputFieldOn:model) aspect:#value3; change:#value3:.

	box open.

	Transcript showCr:'value1: ' , value1.
	Transcript showCr:'value2: ' , value2.
	Transcript showCr:'value3: ' , value3.
"
! !

!EnterFieldGroup methodsFor:'accessing'!

fields
    "return a collection of the inputFields contained in the group."

     ^ fields
!

leaveAction:aBlock
    "set the action to perform when the last field is left.
     Usually, this is to accept the values of all fields and perform
     some additional processing (such as closing a dialog)."

    leaveAction := aBlock
!

wrap:aBoolean
    "specifies if leaving the last field via non-Return
     should wrap back to the first, or leave the group.
     The default is to stay in the input sequence and wrap back to 
     the first field."

    wrap := aBoolean
! !

!EnterFieldGroup methodsFor:'adding / removing'!

add:aField
    |thisIndex action|

    fields isNil ifTrue:[
	fields := OrderedCollection new
    ].
    fields add:aField.
    thisIndex := fields size.

    aField delegate:self.
    aField hideCursor.
    aField disable.

    "set the fields enableAction to disable active field"

    aField clickAction:[:field |
	self makeActive:field
    ].

    "set the fields leaveAction to enable next field"

    aField leaveAction:[:key |
	|next wg explicit nFields nextField|

"/        currentField notNil ifTrue:[
"/            currentField disable.
"/            currentField hideCursor.
"/        ].
"/
	action := key.
	nFields := fields size.

	((key == #CursorUp) or:[key == #PreviousField]) ifTrue:[
	    (thisIndex == 1) ifTrue:[
		next := nFields
	    ] ifFalse:[
		next := thisIndex - 1
	    ]
	].
	((key == #CursorDown) 
	or:[key == #NextField
	or:[key == #Tab]]) ifTrue:[
	    (thisIndex == nFields) ifTrue:[
		next := 1.
		wrap == false ifTrue:[
		    action := #Return.
		].
	    ] ifFalse:[
		next := thisIndex + 1
	    ]
	].
	(action == #Return) ifTrue:[
	    (thisIndex == nFields) ifTrue:[
		leaveAction notNil ifTrue:[
		    currentField := nil.
		    leaveAction value.
		    next := nil
		] ifFalse:[
		    next := 1
		]
	    ] ifFalse:[
		next := thisIndex + 1
	    ]
	].
	next notNil ifTrue:[
	    nextField := fields at:next.

	    explicit := false.
	    (wg := currentField windowGroup) notNil ifTrue:[
		wg focusView == currentField ifTrue:[
		    explicit := true.
		]
	    ].
	    explicit ifTrue:[
		wg focusView:nextField.
	    ] ifFalse:[
		self makeActive:nextField 
	    ]
	]
    ].

    fields size == 1 ifTrue:[
	"the first one"
	self makeActive:aField
    ]
! !

!EnterFieldGroup methodsFor:'event forwarding'!

buttonPress:button x:x y:y view:aView
    "clicking on a field activates it and forwards the click to it"

    self makeActive:aView.
    aView buttonPress:button x:x y:y
!

buttonShiftPress:button x:x y:y view:aView
    "clicking on a field activates it and forwards the click to it"

    self makeActive:aView.
    aView buttonShiftPress:button x:x y:y
!

handlesButtonPress:button inView:aView
    "query from event processor: am I interested in button-events ?
     yes I am (to activate the clicked-on field)."

   ^ true
!

handlesButtonShiftPress:button inView:aView
    "query from event processor: am I interested in button-events ?
     yes I am (to activate the clicked-on field)."

    ^ true
!

handlesKeyPress:key inView:aView
    "query from event processor: am I interested in key-events ?
     yes I am (to forward it to the active field)."

    ^ true
!

handlesKeyRelease:key inView:aView
    "query from event processor: am I interested in key-events ?
     yes I am (to forward it to the active field)."

    ^ true
!

keyPress:key x:x y:y view:aView
    "key-press in any field - forward the key to the active field
     (with nil coordinates to indicate that the key was pressed
      outside. However, this info is not used by any view currently)"

    currentField notNil ifTrue:[
        currentField keyPress:key x:nil y:nil
    ]

    "Modified: 4.3.1996 / 22:18:22 / cg"
!

keyRelease:key x:x y:y view:aView
    "key-release in any field - forward the key to the active field.
     (with -1/-1 as coordinate to indicate that the key was pressed
      outside. However, this info is not used by any view currently)"

    currentField notNil ifTrue:[
	currentField keyRelease:key x:-1 y:-1
    ]
! !

!EnterFieldGroup methodsFor:'misc'!

activateFirst
    "pass controll to my first field"

    fields notNil ifTrue:[
        self makeActive:fields first
    ]

    "Modified: 7.2.1996 / 15:23:09 / cg"
! !

!EnterFieldGroup methodsFor:'private'!

makeActive:aField
    "make a specific field the active one"

    currentField == aField ifTrue:[^ self].

    currentField notNil ifTrue:[
	currentField disable.
	currentField hideCursor.
	currentField hasKeyboardFocus:false.
    ].
    currentField := aField.
    currentField enable.
    currentField showCursor.
    currentField hasKeyboardFocus:true.
! !

!EnterFieldGroup class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libwidg/EnterFieldGroup.st,v 1.20 1996-03-04 22:32:09 cg Exp $'
! !