Model.st
author sr
Mon, 13 May 2019 16:23:44 +0200
changeset 4262 b69be50550bb
parent 4177 871e7b63f408
child 4269 f2ee0d19a7bc
permissions -rw-r--r--
#BUGFIX by Stefan Reise class: ActiveHelp changed: #basicHelpTextFor:at: care for nil in #aDevicePointOrNil

"{ Encoding: utf8 }"

"
 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.
"
"{ Package: 'stx:libview2' }"

"{ NameSpace: Smalltalk }"

Object subclass:#Model
	instanceVariableNames:'dependents'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Support-Models'
!

Query subclass:#ModelUpdateLockedQuery
	instanceVariableNames:''
	classVariableNames:''
	poolDictionaries:''
	privateIn:Model
!

!Model 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
"
    Models are things which represent information models, i.e. something
    which holds the data for user interface components and on which these operate.
    Basically, instances keep track of which components depend on them and 
    inform the dependents of any changes.

    The Model class itself is abstract and not too useful, see subclasses,
    especially, ValueHolder and SelectionInList.

    Notice:
      Actually the Model class is not really needed; since the dependency
      mechanism is inherited by Object, you can take any object as a model.
      However, instances of Model (and subclasses) keep the dependents locally
      in an instance variable; thus speeding up access a bit.

      Model redefines the dependency methods to use non-weak dependencies
      (in contrast to weak dependencies used by Object).  

    [Instance variables:]
        dependents      <Collection>    those objects which depend on me.
                                        To save some storage, a single dependent 
                                        is kept directly here.
                                        Otherwise, if there are multiple dependents,
                                        a collection of dependents is held here.

    [author:]
        Claus Gittinger

    [see also:]
        ValueHolder SelectionInList
        ( introduction to view programming :html: programming/viewintro.html#MVC )
"
! !

!Model class methodsFor:'queries'!

isAbstract
    ^ self == Model
! !

!Model methodsFor:'converting'!

skippedInLiteralEncoding
    "define the inst-slots which are skipped when generating a literalArrayEncoding;
     (to skip the ones with default values.)"

    ^ OrderedCollection with:#dependents

    "Modified (comment): / 09-08-2018 / 17:17:57 / Claus Gittinger"
! !

!Model methodsFor:'copying'!

postCopy
    "release dependents after copying"

    self dependents:nil
!

skipInstvarIndexInDeepCopy:index
    "a helper for deepCopy; only indices for which this method returns
     false are copied in a deep copy."

    ^ index == 1    "/ skip dependents
! !

!Model methodsFor:'dependents access'!

addDependent:anObject
    "make the argument, anObject be a dependent of the receiver"

    |wasBlocked|

    wasBlocked := OperatingSystem blockInterrupts.
    [
        |deps|

        deps := dependents.
        "/
        "/ store the very first dependent directly in
        "/ the dependents instVar
        "/
        (deps isNil and:[anObject isCollection not]) ifTrue:[
            dependents := anObject
        ] ifFalse:[
            "/
            "/ store more dependents in the dependents collection
            "/
            deps isCollection ifTrue:[
                deps add:anObject
            ] ifFalse:[
                deps == anObject ifFalse:[
                    deps isNil ifTrue:[
                        dependents := IdentitySet with:anObject.
                    ] ifFalse:[
                        dependents := IdentitySet with:deps with:anObject.
                    ]
                ]
            ]
        ]
    ] ensure:[
        wasBlocked ifFalse:[
            OperatingSystem unblockInterrupts
        ]
    ]

    "Modified: / 08-01-1997 / 23:40:30 / cg"
    "Modified (format): / 07-02-2018 / 11:27:16 / stefan"
!

breakDependents
    "remove all dependencies from the receiver"

    dependents := nil

    "Created: / 08-02-2017 / 00:46:34 / cg"
!

dependents
    "return a Collection of dependents"

    dependents isNil ifTrue:[^ #()].
    dependents isCollection ifTrue:[
        ^ dependents
    ].
    ^ IdentitySet with:dependents

    "Modified: / 26.1.1998 / 11:18:24 / cg"
!

dependents:aCollectionOrNil
    "set the collection of dependents"

    |dep|

    aCollectionOrNil size == 1 ifTrue:[
        dep := aCollectionOrNil first.
        dep isCollection ifFalse:[
            dependents := dep.
            ^ self
        ]
    ].
    dependents := aCollectionOrNil

    "Modified: / 19-04-1996 / 12:23:05 / cg"
    "Modified: / 07-02-2018 / 12:00:33 / stefan"
!

dependentsDo:aBlock
    "evaluate aBlock for all of my dependents"

    |deps|

    deps := dependents.
    deps notNil ifTrue:[
        deps isCollection ifTrue:[
            deps size == 1 ifTrue:[
                aBlock value:deps anElement
            ] ifFalse:[
                deps copy do:aBlock
            ]
        ] ifFalse:[
            aBlock value:deps
        ]
    ]
!

removeDependent:anObject
    "make the argument, anObject be independent of the receiver"

    |wasBlocked|

    "/ must do this save from interrupts, since the dependents collection
    "/ is possibly accessed from multiple threads.
    "/ Used to use #valueUninterruptably here; inlined that code for slightly
    "/ faster execution.

    wasBlocked := OperatingSystem blockInterrupts.
    [
        |deps sz dep|

        deps := dependents.
        deps notNil ifTrue:[
            deps isCollection ifTrue:[
                dep := deps remove:anObject ifAbsent:[].
                "if dep is nil, nothing has changed"
                dep notNil ifTrue:[
                    (sz := deps size) == 0 ifTrue:[
                        dependents := nil
                    ] ifFalse:[
                        sz == 1 ifTrue:[
                            (dep := deps first) isCollection ifFalse:[
                                dependents := dep
                            ]
                        ]
                    ].
                ].
            ] ifFalse:[
                deps == anObject ifTrue:[
                    dependents := nil
                ]
            ]
        ]
    ] ensure:[
        wasBlocked ifFalse:[
            OperatingSystem unblockInterrupts
        ]
    ]

    "Modified: 8.1.1997 / 23:41:39 / cg"
! !

!Model methodsFor:'dependents access (non weak)'!

addNonWeakDependent:anObject
    "make the argument, anObject be a dependent of the receiver.
     Since all dependencies are nonWeak in Model, this is simply
     forwarded to addDependent:"

    ^ self addDependent:anObject

    "Created: 19.4.1996 / 10:28:53 / cg"
!

interests
    "return a Collection of interests - empty if there is none.
     Here, we use the normal dependents collection for interests."

    ^ self dependents

    "Created: / 19.4.1996 / 12:28:23 / cg"
    "Modified: / 14.10.1996 / 22:19:58 / stefan"
    "Modified: / 30.1.1998 / 14:07:43 / cg"
!

nonWeakDependents
    "return a Collection of dependents - empty if there is none.
     Since all dependencies are nonWeak in Model, this is a dummy."

    ^ self dependents

    "Created: / 19.4.1996 / 10:29:43 / cg"
    "Modified: / 30.1.1998 / 14:06:17 / cg"
!

removeNonWeakDependent:anObject
    "make the argument, anObject be independent of the receiver.
     Since all dependencies are nonWeak in Model, this is simply
     forwarded to removeDependent:"

    ^ self removeDependent:anObject

    "Created: 19.4.1996 / 12:19:40 / cg"
! !

!Model methodsFor:'suppressing updates'!

isLocked
    "ask the updateLocked query."

    ^ ModelUpdateLockedQuery isLocked:self
!

withUpdatesLockedDo:aBlock
    "execute aBlock with updates to myself temporarily suppressed,
     (i.e. by answering true to the updateLocked query)
     This is a cooperative interface - i.e. possible updaters MUST
     check me via isLocked, before sending #value"

    ModelUpdateLockedQuery answer:true for:self do:aBlock
!

withoutSendingUpdatesDo:aBlock
    "execute aBlock with updates to dependents temporarily suppressed
     (i.e. I will not notify the dependents during aBlock)
     Warning: 
        do not change any dependencies inside the block,
        as the original dependencies from before will be restored and such changes
        are lost then."

    |previousDependents|

    previousDependents := dependents.
    [
        dependents := nil.
        aBlock value
    ] ensure:[
        dependents := previousDependents
    ].

    "Modified: / 14-09-2018 / 14:49:47 / Stefan Vogel"
! !

!Model::ModelUpdateLockedQuery class methodsFor:'documentation'!

documentation
"
    this query can answer true (ie. ''isLocked'') for a particular model,
    while inside a updating block.

    This is used for temporal blocking models to be changed
    (eg. to prevent cyclic updates).

    A particular application of this is found in input-field + tree selection
    combinations, where entering a new value into the field changes the treeselection,
    and also tree selection changes the input field's value.
    Concrete: take a look at the XMLInspector's xpath field, which triggers a tree
    selection change, but the selection change should not backfire on the field in this
    case, whereas a regular tree-selection will change the field.

    Notice:
        do NOT place the query into the general value: method of Model.
        This is too expensive to be done every time.
        Thus, the related models need to know about the interlocking and
        must do this individually.

    see example

    [author:]
        cg

    [instance variables:]

    [class variables:]

    [see also:]

"
!

example
"
    |vh1 vh2|

    vh1 := ValueHolder new.
    vh1 onChangeEvaluate:[
        (vh2 isLocked) ifFalse:[
            vh2 value: #newValue 
        ].
    ].

    vh2 := ValueHolder new.
    vh2 value:#oldValue.

    vh2 withUpdatesLockedDo:[
        vh1 value:123.
    ].

    because of the lock, the old value should still be there'.
    self assert:(vh2 value == #oldValue)
"
! !

!Model::ModelUpdateLockedQuery class methodsFor:'handling'!

answer:aBoolean for:aModel do:aBlock
    "evaluate aBlock, answering aBoolean if asked for the locking status of aModel"

    aBlock
        on:self 
        do:[:ex |
            ex proceedWith:(ex parameter == aModel)
        ]
! !

!Model::ModelUpdateLockedQuery class methodsFor:'queries'!

isLocked:aModel
    "asking for the locking status of aModel"

    ^ self raiseRequestWith:aModel
! !

!Model::ModelUpdateLockedQuery methodsFor:'defaults'!

defaultResumeValue
    ^ false
! !

!Model class methodsFor:'documentation'!

version
    ^ '$Header$'
! !