"
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.
"
Object subclass:#WindowBuilder
instanceVariableNames:'window application bindings visuals focusSequence namedComponents
helpKeys componentCreationHook applicationClass keyboardProcessor
subCanvasSpecs'
classVariableNames:'StopOnError'
poolDictionaries:''
category:'Interface-Support-UI'
!
!WindowBuilder 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
"
a no-op class, for systems which do not use the UIBuilder.
Concrete subclasses know how to create a view (with components) from
some interface spec.
Currently, an experimantal version of UIBuilder exists,
and more may be added in the future (for example, to parse different UI
specs - thinking of motifs UIL specs, Windows DialogSpecs etc.).
[instance variables:]
window <View>
the topView into which the components
are (have been) created from the specification
application <ApplicationModel>
the application object (typically an instance
of a subclass of ApplicationModel).
That one is usually supposed to provide
valueHolders for aspects, action methods
menuSpecs and possibly more windowSpecs.
Usually means, that most of those can also be
provided to the builder via a set of bindings,
which overrides those.
bindings <Dictionary>
can be set (or filled) with bindings for
aspects to be used when setting up the models
for components. Useful either to overwrite
corresponding appModel aspects or if the appModel
does not want to procide those.
(for example, to open a dialog and provide the
bindings in a dictionary - as opposed to
providing them via aspect methods)
visuals not yet used - for compatibility
focusSequence <Collection>
maintained during the build process;
contains tabable components.
This will be replaced by a more intelligent
mechanism in the near future.
namedComponents <Dictionary>
contains name->component associations for
all components which have a non-nil component
name. Created during the build process.
helpKeys not yet used - for compatibility
componentCreationHook <BlockOrNil>
can be set before the components are built
from the spec, to provide an arbitrary
callBacks-hook which will be invoked after
a component has been created from a spec.
The UIPainter uses this to maintain its
component<->spec assiciations.
Can be set by the app, to catch creation of
components and fiddle around during the
creation process (change extents, colors or whatever)
applicationClass <ClassOrNil>
can be set to provide an additional class which
is asked for aspects during the build process.
If not set, the app is asked, which itself asks
its class.
keyboardProcessor not yet used - for compatibility
Will eventually takeover the functionality
of the focusSequence, shortcuts & provide a hook
for the app.
subCanvasSpecs <Dictionary>
can be set by the app to provide subcanvas
specs (much like the bindings dictionary)
[author:]
Claus Gittinger
"
! !
!WindowBuilder methodsFor:'accessing'!
addBindings:moreBindings
"used with subDialogs, which provide local bindings.
All bindings from moreBindings overwrite any local bindings."
moreBindings notNil ifTrue:[
moreBindings keysAndValuesDo:[:aKey :aValue |
bindings at:aKey put:aValue
]
]
"Created: 28.2.1997 / 14:14:33 / cg"
!
application
"return the application (an ApplicationModel),
for which the view is built.
This one is supposed to provide the aspects, menus etc."
^ application
"Modified: 17.1.1997 / 19:04:40 / cg"
!
application:anApplicationModel
"set the application (an ApplicationModel),
for which the view is built.
This one is supposed to provide the aspects, menus etc."
application := anApplicationModel
"Modified: 17.1.1997 / 19:04:47 / cg"
!
applicationClass:something
"set the value of the instance variable 'applicationClass' (automatically generated)"
applicationClass := something.!
aspectAt:aSymbol
|b|
aSymbol notNil ifTrue:[
bindings notNil ifTrue:[
b := bindings at:aSymbol ifAbsent:nil.
b notNil ifTrue:[^ b].
].
application notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
ex parameter selector == aSymbol ifFalse:[
ex reject
]
] do:[
^ application aspectFor:aSymbol
].
Object messageNotUnderstoodSignal handle:[:ex |
ex parameter selector == aSymbol ifFalse:[
ex reject
]
] do:[
^ application class aspectFor:aSymbol
].
Transcript showCR:'WindowBuilder: no aspect for ' , aSymbol storeString.
StopOnError == true ifTrue:[self halt]. "/ avoids debugger in end-user apps
].
].
^ nil
"Modified: / 29.10.1997 / 17:26:16 / cg"
!
aspectAt:aSymbol put:aModel
bindings isNil ifTrue:[
bindings := IdentityDictionary new
].
^ bindings at:aSymbol put:aModel
"Modified: 17.1.1997 / 19:28:37 / cg"
!
bindingAt:aSymbol
bindings notNil ifTrue:[
^ bindings at:aSymbol ifAbsent:nil.
].
^ nil
!
bindings
^ bindings
!
bindings:aDictionary
bindings := aDictionary
!
componentAt:name
namedComponents isNil ifTrue:[^ nil].
^ namedComponents at:name asSymbol ifAbsent:nil
!
componentAt:name put:aComponent
namedComponents isNil ifTrue:[
namedComponents := IdentityDictionary new.
].
namedComponents at:name asSymbol put:aComponent
!
componentCreationHook:something
"set the value of the instance variable 'componentCreationHook' (automatically generated)"
componentCreationHook := something.!
focusSequence
^ focusSequence
!
helpKeyFor:aComponent
|v key|
helpKeys isNil ifTrue:[^ nil].
v := aComponent.
[v notNil] whileTrue:[
(key := helpKeys at:v ifAbsent:nil) notNil ifTrue:[
^ key
].
v := v superView
].
^ nil
!
helpKeyFor:aComponent put:aKey
aKey isNil ifTrue:[
helpKeys isNil ifFalse:[
helpKeys removeKey:aComponent ifAbsent:nil
]
] ifFalse:[
helpKeys isNil ifTrue:[
helpKeys := IdentityDictionary new
].
helpKeys at:aComponent put:aKey
]
!
keyboardProcessor
keyboardProcessor isNil ifTrue:[
keyboardProcessor := KeyboardProcessor new
].
^ keyboardProcessor
"Created: 3.3.1997 / 18:31:37 / cg"
"Modified: 3.3.1997 / 18:32:27 / cg"
!
namedComponents
^ namedComponents
!
source
"same as #application, for ST-80 compatibility"
^ application
"Created: 17.1.1997 / 19:03:51 / cg"
!
source:anApplicationModel
"same as #application:, for ST-80 compatibility"
application := anApplicationModel
"Modified: 17.1.1997 / 19:03:57 / cg"
!
subCanvasAt:majorKey at:minorKey
"get the subCanvas or subSpec specification from major and minor key.
Here, we first look for a spec in the private subCanvasSpecs dictionary,
which can be filled via #specificationAt:at:put: messages.
If not present, or none is found there, we bounce back trying
#specificationFor: (if majorKey is nil) or by sending the minorKey
message to the class named as majorKey.
Notice, that the class named majorKey is first searched in the
application classes namespace - allowing private classes as majorKey.
"
|spec cls dict dkey|
subCanvasSpecs notNil ifTrue:[
dkey := majorKey ? #NoMajorKey.
dict := subCanvasSpecs at:dkey ifAbsent:nil.
dict notNil ifTrue:[
spec := dict at:minorKey ifAbsent:nil.
spec notNil ifTrue:[^ spec].
].
].
majorKey isNil ifTrue:[
spec := self specificationFor:minorKey.
"/ try if application or applicationClass respond to minorKey
"/ (possible if not a subclass of ApplicationModel)
spec isNil ifTrue:[
application messageNotUnderstoodSignal handle:[:ex |
ex proceed.
] do:[
application notNil ifTrue:[
spec := application perform:minorKey.
].
(spec isNil and:[applicationClass notNil]) ifTrue:[
spec := applicationClass perform:minorKey.
].
].
].
] ifFalse:[
application notNil ifTrue:[
"/ look for class in applications namespace ...
cls := application resolveName:majorKey.
] ifFalse:[
"/ fallBack - use that global, if it exists
cls := Smalltalk at:majorKey.
cls isNil ifTrue:[
Transcript showCR:('WindowBuilder[warning]: missing application when fetching majorKey:' , majorKey).
].
].
cls notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
ex proceed.
] do:[
spec := cls specificationFor:minorKey.
spec isNil ifTrue:[
spec := cls perform:minorKey.
].
].
].
].
^ spec
"Modified: / 27.1.1998 / 12:17:13 / cg"
"Modified: / 6.2.1998 / 12:31:45 / stefan"
!
subCanvasAt:majorKey at:minorKey put:aSpec
"deposit an interfaceSpecification for major and minor key
in my private subCanvasSpecs dictionary.
This will be used later, when building,
to provide an interfaceSpec for a subcanvas or subSpecification
(or possibly override an application provided interfaceSpec).
See #subCanvasAt:at:."
|dict key|
subCanvasSpecs isNil ifTrue:[
"/ lazyly initialize
subCanvasSpecs := IdentityDictionary new
].
key := majorKey ? #NoMajorKey.
dict := subCanvasSpecs at:key ifAbsent:nil.
dict isNil ifTrue:[
dict := IdentityDictionary new.
subCanvasSpecs at:key put:dict
].
dict at:minorKey put:aSpec
"Modified: / 27.1.1998 / 12:21:27 / cg"
"Modified: / 5.2.1998 / 12:05:32 / stefan"
!
visualAt:name
visuals isNil ifTrue:[^ nil].
^ visuals at:name asSymbol ifAbsent:nil
"Created: 3.3.1997 / 16:24:17 / cg"
!
visualAt:name put:aVisual
visuals isNil ifTrue:[
visuals := IdentityDictionary new.
].
visuals at:name asSymbol put:aVisual
"Created: 3.3.1997 / 16:24:41 / cg"
!
visuals
^ visuals
"Created: 3.3.1997 / 16:24:00 / cg"
!
visuals:aDictionary
visuals := aDictionary
"Created: 3.3.1997 / 16:24:06 / cg"
!
window
"return the top window (view), for which an interface
is (being) built"
^ window
"Modified: 17.1.1997 / 19:30:00 / cg"
!
window:aView
"set the top window (view), for which an interface
is (being) built"
window := aView
"Modified: 17.1.1997 / 19:30:22 / cg"
!
windowGroup
^ window windowGroup
"Modified: 17.6.1997 / 18:04:01 / cg"
! !
!WindowBuilder methodsFor:'aspect access support'!
booleanValueAspectFor:aKey
"helper (common code) to generate a boolean aspect if required.
If no binding exists for aKey, a valueHolder holding false is
created and added to the bindings.
Otherwise, the existing binding is returned."
^ self valueAspectFor:aKey initialValue:false
"Modified: 28.7.1997 / 12:53:57 / cg"
!
nilValueAspectFor:aKey
"helper (common code) to generate a valueHolder aspect if required.
If no binding exists for aKey, a valueHolder holding nil is
created and added to the bindings.
Otherwise, the existing binding is returned."
^ self valueAspectFor:aKey initialValue:nil
"Modified: 28.7.1997 / 12:54:06 / cg"
!
valueAspectFor:aKey initialValue:initialValue
"helper (common code) to generate a valueHolder aspect if required.
If no binding exists for aKey, a valueHolder holding initialValue is
created and added to the bindings.
Otherwise, the existing binding is returned."
|holder|
(holder := self bindingAt:aKey) isNil ifTrue:[
self aspectAt:aKey put:(holder := initialValue asValue).
].
^ holder
"Created: 28.7.1997 / 12:53:45 / cg"
"Modified: 28.7.1997 / 12:54:13 / cg"
! !
!WindowBuilder methodsFor:'building'!
buildFromSpec:aSpec
^ self subclassResponsibility
!
makeTabable:aComponent
"add a component to the list of tabable components"
focusSequence isNil ifTrue:[
focusSequence := OrderedCollection new.
].
focusSequence add:aComponent
"Modified: / 31.10.1997 / 18:39:30 / cg"
! !
!WindowBuilder methodsFor:'message sending'!
safelyPerform:aSelector
"send the message aSelector to the application;
the result returned from the send or nil is returned
"
|res|
aSelector isSymbol ifTrue:[
application notNil ifTrue:[
application messageNotUnderstoodSignal handle:[:ex |
] do:[
(res := application perform:aSelector) notNil ifTrue:[^ res]
]
].
applicationClass notNil ifTrue:[
applicationClass messageNotUnderstoodSignal handle:[:ex |
] do:[
(res := applicationClass perform:aSelector) notNil ifTrue:[^ res]
]
]
].
^ nil
"Modified: / 5.2.1998 / 12:28:23 / stefan"
!
safelyPerform:aSelector with:anArgument
"send the one-arg-message aSelector to the application;
the result returned from the send or nil is returned
"
|res|
aSelector isSymbol ifTrue:[
application notNil ifTrue:[
application messageNotUnderstoodSignal handle:[:ex |
] do:[
(res := application perform:aSelector with:anArgument) notNil ifTrue:[^ res]
]
].
applicationClass notNil ifTrue:[
applicationClass messageNotUnderstoodSignal handle:[:ex |
] do:[
(res := applicationClass perform:aSelector with:anArgument) notNil ifTrue:[^ res]
]
].
].
^ nil
"Modified: / 5.2.1998 / 12:28:54 / stefan"
! !
!WindowBuilder methodsFor:'spec creation aspect fetch'!
actionFor:aKey
"return an action for aKey. This is invoked during window building
(by the builder) to ask for an ActionButtons actionBlock.
Here, first the local bindings are searched, then the application and
finally the applications class is asked for a corresponding action.
The returned object is typically a block."
|b|
bindings notNil ifTrue:[
b := bindings at:aKey ifAbsent:nil.
b notNil ifTrue:[^ b].
].
application notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
] do:[
^ application actionFor:aKey
]
].
applicationClass notNil ifTrue:[
(applicationClass respondsTo:#actionFor:) ifTrue:[
^ applicationClass actionFor:aKey
]
].
Transcript showCR:'WindowBuilder: no action for: ' , aKey storeString.
StopOnError == true ifTrue:[self halt]. "/ avoids debugger in end-user apps
^ []
"Created: / 17.1.1997 / 21:08:22 / cg"
"Modified: / 28.10.1997 / 12:52:57 / cg"
!
actionFor:aKey withValue:aValue
"return an action for aKey/value combonation.
This is invoked during window building
(by the builder) to ask for an ActionButtons actionBlock if that button
specified an action with an argument value.
Here, first the local bindings are searched, then the application and
finally the applications class is asked for a corresponding action.
The returned object is typically a block."
|b|
bindings notNil ifTrue:[
b := bindings at:aKey ifAbsent:nil.
b notNil ifTrue:[^ b].
].
application notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
] do:[
^ application actionFor:aKey withValue:aValue
]
].
applicationClass notNil ifTrue:[
(applicationClass respondsTo:#actionFor:withValue:) ifTrue:[
^ applicationClass actionFor:aKey withValue:aValue
]
].
Transcript showCR:'WindowBuilder: no action for: ' , aKey storeString.
StopOnError == true ifTrue:[self halt]. "/ avoids debugger in end-user apps
^ [:dummy | ]
"Created: / 17.1.1997 / 21:08:22 / cg"
"Modified: / 28.10.1997 / 12:53:22 / cg"
!
aspectFor:aKey
"return a model for aKey. This is invoked during window building
(by the builder) to ask for an Editfields, a Toggles etc. model.
Here, first the local bindings are searched, then the application and
finally the applications class is asked for a corresponding action.
The returned object is typically a valueHolder."
|b|
bindings notNil ifTrue:[
b := bindings at:aKey ifAbsent:nil.
b notNil ifTrue:[^ b].
].
application notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
] do:[
^ application aspectFor:aKey
]
].
applicationClass notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
] do:[
^ applicationClass aspectFor:aKey
]
].
^ self aspectAt:aKey
"Created: / 17.1.1997 / 21:06:16 / cg"
"Modified: / 1.11.1997 / 13:40:24 / cg"
!
componentFor:aKey
"return a component for aKey. This is invoked during window building
(by the builder) to ask for an ArbitraryComponents view.
Here, first the local bindings are searched, then the application and
finally the applications class is asked for a corresponding action.
The returned object is typically a view."
|component|
application notNil ifTrue:[
component := application componentFor:aKey.
component notNil ifTrue:[^ component].
].
applicationClass notNil ifTrue:[
(applicationClass respondsTo:#componentFor:) ifTrue:[
^ applicationClass componentFor:aKey
]
].
^ self aspectAt:aKey
"Modified: 20.6.1997 / 11:40:22 / cg"
!
labelFor:aKey
"return a label for aKey. This is invoked during window building
(by the builder) to ask for a ???'s label.
Here, first the local bindings are searched, then the application and
finally the applications class is asked for a corresponding action.
The returned object is typically a string."
application notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
] do:[
^ application labelFor:aKey
]
].
applicationClass notNil ifTrue:[
(applicationClass respondsTo:#labelFor:) ifTrue:[
^ applicationClass labelFor:aKey
]
].
"/ Transcript showCR:'WindowBuilder: no label for: ' , aKey storeString.
"/ StopOnError == true ifTrue:[self halt]. "/ avoids debugger in end-user apps
^ self aspectAt:aKey
"Modified: / 28.10.1997 / 12:54:10 / cg"
!
listFor:aKey
"return a list for aKey. This is invoked during window building
(by the builder) to ask for a ???'s label.
Here, first the local bindings are searched, then the application and
finally the applications class is asked for a corresponding action.
The returned object is typically a list."
application notNil ifTrue:[
Object messageNotUnderstoodSignal handle:[:ex |
] do:[
^ application listFor:aKey
]
].
applicationClass notNil ifTrue:[
(applicationClass respondsTo:#listFor:) ifTrue:[
^ applicationClass listFor:aKey
]
].
"/ Transcript showCR:'WindowBuilder: no list for: ' , aKey storeString.
"/ StopOnError == true ifTrue:[self halt]. "/ avoids debugger in end-user apps
^ self aspectAt:aKey
"Created: / 17.1.1997 / 21:08:45 / cg"
"Modified: / 28.10.1997 / 12:54:32 / cg"
!
specificationFor:aKey
"return a specification for aKey. This is invoked during window building
(by the builder) to ask for the interfaceSpec for a subCanvas or subSpecification.
Here, first the local bindings are searched, then the application and
finally the applications class is asked for a corresponding interfaceSPec.
The returned object is typically an interfaceSpec array."
|spec|
application notNil ifTrue:[
application messageNotUnderstoodSignal handle:[:ex |
] do:[
spec := application specificationFor:aKey.
].
spec notNil ifTrue:[^ spec].
].
applicationClass notNil ifTrue:[
(applicationClass respondsTo:#specificationFor:) ifTrue:[
^ applicationClass specificationFor:aKey
]
].
^ self aspectAt:aKey
"Modified: / 20.6.1997 / 11:40:22 / cg"
"Created: / 5.2.1998 / 10:47:46 / stefan"
"Modified: / 5.2.1998 / 12:25:08 / stefan"
! !
!WindowBuilder methodsFor:'spec creation callbacks'!
createdComponent:aView forSpec:spec named:name
"callback from the UISpec after a view has been
created for a spec.
If it has a name, add it to the namedComponents dictionary;
if I have a creationHook (application callBack), evaluate it."
name notNil ifTrue:[
"/ self componentAt:name put:aView.
namedComponents isNil ifTrue:[
namedComponents := IdentityDictionary new.
].
namedComponents at:name asSymbol put:aView
].
componentCreationHook notNil ifTrue:[
componentCreationHook value:aView value:spec value:self
]
"Modified: / 5.9.1995 / 21:42:54 / claus"
"Created: / 31.10.1997 / 18:47:01 / cg"
"Modified: / 31.10.1997 / 18:51:22 / cg"
! !
!WindowBuilder methodsFor:'startup'!
closeRequest
window destroy
"Modified: 17.1.1997 / 19:30:32 / cg"
!
open
"open my topView, as previously created"
|type|
application isNil ifTrue:[
type := #normal
] ifFalse:[
type := application defaultWindowType
].
self
openWithExtent:nil
andType:type
"Modified: 3.3.1997 / 19:43:57 / cg"
!
openAt:aPoint
"open my topView at some location"
self
openAt:aPoint
withExtent:nil
andType:(application defaultWindowType)
"Created: 14.2.1997 / 20:21:57 / cg"
"Modified: 28.2.1997 / 22:50:29 / cg"
!
openAt:origin withExtent:ext andType:type
"open my window, as previously created, optionally defining the
windows origin and/or extent.
The type argument may be #dialog or #normal, and specifies if the view
should be opened as a modal view, blocking interaction to the currently
active view, or as a normal view."
origin notNil ifTrue:[
window origin:origin
].
ext notNil ifTrue:[
window extent:ext.
] ifFalse:[
type == #dialog ifTrue:[
window fixSize
]
].
type == #dialog ifTrue:[
window fixPosition:(window device pointerPosition - window positionOffset).
window openModal.
^ self
].
type == #normal ifTrue:[
window isNil ifTrue:[
self setupWindowFor:(StandardSystemView new).
].
window open.
^ self
].
type == #popUp ifTrue:[
window fixPosition:(window device pointerPosition).
window openAsPopUp.
^ self
].
"
if ST-80 supports more types - these may be added later
"
self halt:'unimplemented'
"Created: / 14.2.1997 / 20:22:24 / cg"
"Modified: / 27.1.1998 / 12:28:18 / cg"
!
openDialog
"open my topView, as previously created as a modal view,
blocking interaction to the currently active view."
self
openWithExtent:nil
andType:#dialog
"Modified: 17.1.1997 / 19:59:29 / cg"
!
openDialogAt:aPoint withExtent:ext
"open my topView, as previously created as a modal view,
blocking interaction to the currently active view."
self
openAt:aPoint withExtent:ext
andType:#dialog
"Modified: 17.1.1997 / 19:59:36 / cg"
"Created: 14.2.1997 / 20:24:19 / cg"
!
openDialogWithExtent:ext
"open my topView, as previously created as a modal view,
blocking interaction to the currently active view."
self
openWithExtent:ext
andType:#dialog
"Modified: 17.1.1997 / 19:59:36 / cg"
!
openModal
"open my topView, as previously created"
self
openWithExtent:nil
andType:#dialog
"Modified: 3.3.1997 / 19:43:57 / cg"
!
openPopUpAt:aPoint
"open my topView, as previously created as a popUp view,
blocking interaction to the currently active view."
self
openAt:aPoint withExtent:nil
andType:#popUp
"Modified: 17.1.1997 / 19:59:29 / cg"
"Created: 14.2.1997 / 20:24:38 / cg"
!
openPopUpIn:aRectangle
"open my topView, as previously created as a popUp view,
blocking interaction to the currently active view."
self
openWithExtent:nil
andType:#popUp
"Modified: 17.1.1997 / 19:59:29 / cg"
"Created: 17.1.1997 / 20:01:24 / cg"
!
openWindowAt:aPoint
"open my topView at some location"
self openAt:aPoint
!
openWindowAt:origin withExtent:ext andType:type
"open my window, as previously created, optionally defining the
windows origin and/or extent.
The type argument may be #dialog or #normal, and specifies if the view
should be opened as a modal view, blocking interaction to the currently
active view, or as a normal view."
^ self openAt:origin withExtent:ext andType:type
!
openWithExtent:aPoint
"open my topView, as previously created, but override
the extent."
self
openWithExtent:aPoint
andType:(application defaultWindowType)
"Modified: 17.1.1997 / 19:58:48 / cg"
!
openWithExtent:ext andType:type
"open my window, as previously created. The type argument
may be #dialog or #normal, and specifies if the view should
be opened as a modal view, blocking interaction to the currently
active view, or as a normal view."
^ self openAt:nil withExtent:ext andType:type
"Modified: 14.2.1997 / 20:22:47 / cg"
! !
!WindowBuilder class methodsFor:'documentation'!
version
^ '$Header: /cvs/stx/stx/libview2/Attic/WinBuilder.st,v 1.58 1998-02-06 12:24:12 stefan Exp $'
! !