Controller.st
author Claus Gittinger <cg@exept.de>
Thu, 01 Aug 1996 18:17:39 +0200
changeset 1012 e408830caf2d
parent 744 db883c9b74b8
child 1036 e4b2a5b5b0df
permissions -rw-r--r--
no longer send buttonShiftPress messages (has to be done in the buttonPress method)

"
 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:#Controller
	instanceVariableNames:'model view sensor'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Support-Controllers'
!

!Controller  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
"
    Controllers can be used to controll the user-interactions
    to a model which is shown in a view. 

    For very simple views (and due to the evolution of Smalltalk/X),
    many view-classes have the controller function integrated.

    To allow both controller and non-controller operation, events are
    sent directly to the view, if the view has no controller
    (i.e. if its controller instance variable is nil). 
    Otherwise, the controller gets the event message.

    For now (vsn 2.10.4) there are only a few view classes using controllers;
    however, over time, more will be converted, since separating the controller
    offers much more flexibility 
    (although view initialization becomes a bit more complex).

    Over time, expect the buttonPress/Release/Motion and keyPress/Release
    methods to vanish from the view classes and corresponding controllers to
    be appearing.
    This migration should be backward compatible.

    Device coordinates vs. Logical coordinates:
        if the view has a non-identity transformation (for example: drawing
        in millimeters or inches), the application/controller may or may not
        be interrested in device coordinates in button/key events.
        Most are not, these will receive logical coordinates transparently
        in theyr button/key-Press/Release/Motion methods.
        Those which are interrested should redefine the corresponding
        device-Key/Button-Press/Release/Motion methods.
        Of course, it is always possible to map between device and logical
        coordinates, using `view transformation applyTo/applyInverseTo:',
        if both coordinates are required (which is unlikely).

    [Instance variables:]
        view        <View>               the view I control

        model       <Model>              the model which is to be worked on


    [author:]
        Claus Gittinger

    [see also:]
        View Model WindowGroup
        WindowEvent DeviceWorkstation
"
! !

!Controller  class methodsFor:'instance creation'!

new
    ^ self basicNew initialize
! !

!Controller methodsFor:'ST-80 compatibility'!

controlInitialize
    ^ self
!

open
    "open my view"

    view open
! !

!Controller methodsFor:'ST-80 compatibility events'!

blueButtonActivity
    "actually, this should be called 'rightButtonActivity'.
     But for ST-80 compatibility ...."

    ^ self
!

redButtonActivity
    "actually, this should be called 'leftButtonActivity'.
     But for ST-80 compatibility ...."

    ^ self
!

yellowButtonActivity
    "actually, this should be called 'middleButtonActivity'.
     But for ST-80 compatibility ...."

    |menu actionSelector menuPerformer|

    "
     ST/X style static menus - going to be obsoleted ...
    "
    (menu := view middleButtonMenu) notNil ifTrue:[
        menu showAtPointer.
        ^ self
    ].

    menu := self yellowButtonMenu.
    menu notNil ifTrue:[
        menuPerformer := self menuPerformer.

        "
         got one, launch the menu. It is supposed
         to return an actionSelector.
        "
        "
         a temporary kludge: subMenus dont know about 
         actionSelectors yet ...
        "
        menu receiver isNil ifTrue:[
            menu receiver:menuPerformer
        ] ifFalse:[
            "
             if the menu has an explicit receiver, 
             thats the one to do the work.
            "
            menuPerformer := menu receiver
        ].
        actionSelector := menu startUp.

        (actionSelector notNil
        and:[actionSelector isSymbol]) ifTrue:[
            menuPerformer perform:actionSelector
        ].
        ^ self
    ].

    "Modified: 28.2.1996 / 17:32:42 / cg"
! !

!Controller methodsFor:'accessing'!

menuHolder 
    "by default, the model has to provide the menu"

    model isNil ifTrue:[^ view].
    ^ model
!

menuPerformer 
    "by default, the model is performing menu actions"

    model isNil ifTrue:[^ view].
    ^ model
!

model
    "return my model"

    ^ model
!

model:aModel
    "set my model"

    model notNil ifTrue:[
	model removeDependent:view
    ].
    model := aModel.
"/    model notNil ifTrue:[model addDependent:view]
!

sensor 
    "return my views sensor"

    ^ view sensor
!

view
    "return my view"

    ^ view
!

view:aView
    "set my view"

    view notNil ifTrue:[
	model notNil ifTrue:[
	    model removeDependent:view
	]
    ].
    view := aView.
    model notNil ifTrue:[
"/        model addDependent:view
	view notNil ifTrue:[
	    view model:model
	]
    ].
! !

!Controller methodsFor:'event handling'!

buttonMotion:buttonMask x:x y:y
    "mouse was moved with button pressed in my view; nothing done here"

    ^ self
!

buttonMultiPress:button x:x y:y
    "a mouse button was pressed again shortly after in my view"

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

buttonPress:button x:x y:y
    "a mouse button was pressed in my view.
     Translate buttonPress events into similar ST-80 type
     event messages. This method and/or these ST-80 methods
     can be redefined in subclasses"

    ((button == 1) or:[button == #select]) ifTrue:[
	self redButtonActivity
    ].
    ((button == 2) or:[button == #menu]) ifTrue:[
	self yellowButtonActivity
    ].
    (button == 3) ifTrue:[
	self blueButtonActivity
    ]
!

buttonRelease:button x:x y:y
    "a mouse button was released in my view; nothing done here"

    ^ self
!

deviceButtonMotion:state x:x y:y
    "this is the low-level (untransformed) event as received
     from the device (i.e. coordinates are in device coordinates). 
     If there is a transformation, apply the inverse
     and send a buttonMotion with the logical coordinates.

     Controllers which are interrested in deviceCoordinates should
     redefine this method - 
     those which are interrested in logical coordinates
     should redefine #buttonMotion:x:y:"

    |lx ly trans|

    lx := x.
    ly := y.
    (trans := view transformation) notNil ifTrue:[
        lx := trans applyInverseToX:lx.
        ly := trans applyInverseToY:ly.
    ].
    self buttonMotion:state x:lx y:ly

    "Modified: 13.5.1996 / 11:24:11 / cg"
!

deviceButtonMultiPress:button x:x y:y
    "this is the low-level (untransformed) event as received
     from the device (i.e. coordinates are in device coordinates). 
     If there is a transformation, apply the inverse
     and send a buttonMultiPress with the logical coordinates.

     Controllers which are interrested in deviceCoordinates should
     redefine this method - 
     those which are interrested in logical coordinates
     should redefine #buttonMultiPress:x:y:"

    |lx ly trans|

    lx := x.
    ly := y.
    (trans := view transformation) notNil ifTrue:[
        lx := trans applyInverseToX:lx.
        ly := trans applyInverseToY:ly.
    ].
    self buttonMultiPress:button x:lx y:ly

    "Modified: 13.5.1996 / 11:23:54 / cg"
!

deviceButtonPress:button x:x y:y
    "this is the low-level (untransformed) event as received
     from the device (i.e. coordinates are in device coordinates). 
     If there is a transformation, apply the inverse
     and send a buttonPress with the logical coordinates.

     Controllers which are interrested in deviceCoordinates should
     redefine this method - 
     those which are interrested in logical coordinates
     should redefine #buttonPress:x:y:"

    |lx ly trans|

    lx := x.
    ly := y.
    (trans := view transformation) notNil ifTrue:[
        lx := trans applyInverseToX:lx.
        ly := trans applyInverseToY:ly.
    ].
    self buttonPress:button x:lx y:ly

    "Modified: 13.5.1996 / 11:24:23 / cg"
!

deviceButtonRelease:button x:x y:y
    "this is the low-level (untransformed) event as received
     from the device (i.e. coordinates are in device coordinates). 
     If there is a transformation, apply the inverse
     and send a buttonRelease with the logical coordinates.

     Controllers which are interrested in deviceCoordinates should
     redefine this method - 
     those which are interrested in logical coordinates
     should redefine #buttonRelease:x:y:"

    |lx ly trans|

    lx := x.
    ly := y.
    (trans := view transformation) notNil ifTrue:[
        lx := trans applyInverseToX:lx.
        ly := trans applyInverseToY:ly.
    ].
    self buttonRelease:button x:lx y:ly

    "Modified: 13.5.1996 / 11:24:33 / cg"
!

deviceKeyPress:key x:x y:y
    "this is the low-level (untransformed) event as received
     from the device (i.e. coordinates are in device coordinates). 
     If there is a transformation, apply the inverse
     and send a keyPress with the logical coordinates.

     Controllers which are interrested in deviceCoordinates should
     redefine this method - 
     those which are interrested in logical coordinates
     should redefine #keyPress:x:y:"

    |lx ly trans|

    lx := x.
    ly := y.
    (trans := view transformation) notNil ifTrue:[
        lx := trans applyInverseToX:lx.
        ly := trans applyInverseToY:ly.
    ].
    self keyPress:key x:lx y:ly

    "Modified: 13.5.1996 / 11:24:43 / cg"
!

deviceKeyRelease:key x:x y:y
    "this is the low-level (untransformed) event as received
     from the device (i.e. coordinates are in device coordinates). 
     If there is a transformation, apply the inverse
     and send a keyRelease with the logical coordinates.

     Controllers which are interrested in deviceCoordinates should
     redefine this method - 
     those which are interrested in logical coordinates
     should redefine #keyRelease:x:y:"

    |lx ly trans|

    lx := x.
    ly := y.
    (trans := view transformation) notNil ifTrue:[
        lx := trans applyInverseToX:lx.
        ly := trans applyInverseToY:ly.
    ].
    self keyRelease:key x:lx y:ly

    "Modified: 13.5.1996 / 11:24:51 / cg"
!

devicePointerEnter:state x:x y:y
    "this is the low-level (untransformed) event as received
     from the device (i.e. coordinates are in device coordinates). 
     If there is a transformation, apply the inverse
     and send a pointerEnter with the logical coordinates.

     Controllers which are interrested in deviceCoordinates should
     redefine this method - 
     those which are interrested in logical coordinates
     should redefine #pointerEnter:x:y:"

    |lx ly trans|

    lx := x.
    ly := y.
    (trans := view transformation) notNil ifTrue:[
        lx := trans applyInverseToX:lx.
        ly := trans applyInverseToY:ly.
    ].
    self pointerEnter:state x:lx y:ly

    "Modified: 13.5.1996 / 11:24:59 / cg"
!

focusIn
    "my view got the keyboard focus; nothing done here"

    ^ self
!

focusOut
    "my view lost keyboard focus; nothing done here"

    ^ self
!

keyPress:key x:x y:y
    "key was pressed in my view; nothing done here,
     except for Tab keys."

    <resource: #keyboard (#Tab #FocusNext #FocusPrevious)>

    |windowGroup|

    windowGroup := view windowGroup.
    key == #Tab ifTrue:[
        windowGroup notNil ifTrue:[
            view graphicsDevice shiftDown ifTrue:[
                windowGroup focusPrevious
            ] ifFalse:[
                windowGroup focusNext
            ].
        ]
    ].
    key == #FocusNext ifTrue:[
        windowGroup notNil ifTrue:[
            windowGroup focusNext.
        ]
    ].
    key == #FocusPrevious ifTrue:[
        windowGroup notNil ifTrue:[
            windowGroup focusPrevious.
        ]
    ].

    ^ self

    "Modified: 28.5.1996 / 20:21:41 / cg"
!

keyRelease:key x:x y:y
    "key was released in my view; nothing done here"

    ^ self
!

pointerEnter:state x:x y:y
    "mouse pointer entered my view; nothing done here"

    ^ self
!

pointerLeave:state
    "mouse pointer left my view; nothing done here"

    ^ self
! !

!Controller methodsFor:'initialize / release'!

initialize
    "initialize the controller; subclasses should redefine
     this and include a super initialize for proper initialization."

    ^ self
!

release
    "close down the controller; this is sent when the view is destroyed.
     Can be redefined in subclasses to do some cleanup action. However,
     these redefined methods should do a super release."

    view notNil ifTrue:[view controller:nil].
    view := nil.
    model := nil
!

startUp 
    "startup the controller; this is sent when the view realizes,
     right before it becomes visible.
     Can be redefined in subclasses to do some startup action."

    self controlInitialize.
! !

!Controller methodsFor:'menus'!

yellowButtonMenu
    "actually, this should be called 'middleButtonMenu'.
     But for ST-80 compatibility ...."

    |sym menuHolder m|

"/    (m := view middleButtonMenu) notNil ifTrue:[
"/        "/
"/        "/ has been assigned a static middleButtonMenu
"/        "/ (or a cached menu)
"/        "/
"/        ^ m
"/    ].

    menuHolder := self menuHolder.

    "
     try ST-80 style menus first:
     if there is a model, and a menuMessage is defined,
     ask model for the menu and launch that if non-nil.
    "
    (menuHolder notNil 
    and:[(sym := view menuMessage) notNil
    and:[sym isSymbol]]) ifTrue:[
	"
	 ask model for the menu
	"
	^ menuHolder perform:sym.
    ].
    ^ nil
! !

!Controller  class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libview/Controller.st,v 1.37 1996-08-01 16:17:10 cg Exp $'
! !