The browser's operation and UI is typical for a large number of applications:
from simple telephone or address lists, to data base management utilities.
In Smalltalk, the SystemBrowser, FileBrowser and ChangesBrowser are
all of this type.
In the following, we will develop that application as a step-by-step
tutorial. Instead of presenting the complete and finished program,
we will go through all (redesign & change) steps.
We will even go through all little design errors, which
may you may encounter while developping applications.
You will see that things are fixed pretty fast ...
Of course, for more complex applications, a design phase should be
done, instead of ad-hoc hacking ;-)
However, the thing below is simple enough as a one or two hour programming
project - so lets jump right into it ....
The browser's topView shall be divided into an upper panel, which contains a
classList and a methodList.
The classList presents the names of all classes in the system,
the methodList shows the selected classes methods.
Once selected, a methods sourcecode will be shown in the lower part of
the panel (the so called codeView).
The codeView shall offer a popUpMenu with the usual copy-cut-paste
and an additional accept item, to compile and install the changed
methods text.
ApplicationModel
,
which provides a framework for this type of application:
ApplicationModel subclass:#SimpleBrowser
instanceVariableNames:''
classVariableNames:''
poolDictionaries:''
category:'Demos-Tutorial'
ApplicationModel
expects that its subclasses implement a method
named openInterface
to create and setup the application's window.
Lets define it as:
Explanations:
openInterface
"sent by my superclass to open up my interface
(i.e. SimpleBrowser open - openInterface)"
|topView|
topView := StandardSystemView new.
topView label:'Simple Browser'.
topView application:self.
self setupSubviewsIn:topView.
topView open.
"
SimpleBrowser open
"
setupSubviewsIn:
method. This separation is useful,
to allow for the application to be executed as a subview within some other applications view
(only to be flexible in future - who knows ...).
topView application:self.
tells the topView what its underlying application is. Although this is currently
not needed in our example (things work perfectly without it),
it will be useful when the
topView is closed later: if it has been given an application, THAT one gets a notification
first, and the application can decide if termination is really to be done.
(useful for example, to check if some code has been modified and not yet saved).
The setupSubviewsIn:
method creates the panels and adds the two
SelectionInListViews
in the upper part, and a CodeView
in
the bottom half. Its implementation is straight forward:
the above code can be written a bit more compact, by using compound instance
creation methods instead of individual access methods.
Beginners may prefer the above - once you get used to the system
you probably find the code below more readable (use whichever you prefer):
setupSubviewsIn:topView
"create the panels with the selectionInListViews and the codeView"
|outerPanel upperPanel
classListView methodListView
scrollingView1 scrollingView2 scrollingView3|
"
create the panels ...
"
outerPanel := VariableVerticalPanel new.
topView
addSubView:outerPanel
in:(0.0 @ 0.0 corner:1.0 @ 1.0).
upperPanel := VariableHorizontalPanel new
outerPanel
addSubView:upperPanel
in:(0.0 @ 0.0 corner:1.0 @ 0.3).
"
the classListView is a scrollable SelectionInListView ...
"
scrollingView1 := HVScrollableView miniScrollerH:true.
upperPanel
addSubView:scrollingView1
in:(0.0 @ 0.0 corner:0.5 @ 1.0).
classListView := SelectionInListView new.
scrollingView1 scrolledView:classListView.
"
the methodListView also ...
"
scrollingView2 := HVScrollableView miniScrollerH:true.
upperPanel
addSubView:scrollingView2
in:(0.5 @ 0.0 corner:1.0 @ 1.0).
methodListView := SelectionInListView new.
scrollingView2 scrolledView:methodListView.
"
the codeView is a scrollable CodeView ...
"
scrollingView3 := HVScrollableView miniScrollerH:true.
outerPanel
addSubView:scrollingView3
in:(0.0 @ 0.3 corner:1.0 @ 1.0).
codeView := CodeView new.
scrollingView3 scrolledView:codeView.
setupSubviewsIn:topView
"create the panels with the selectionInListViews and the codeView"
|outerPanel upperPanel
classListView methodListView codeView
scrollHelper|
"
create the panels ...
"
outerPanel := VariableVerticalPanel
origin:0.0 @ 0.0 corner:1.0 @ 1.0
in:topView.
upperPanel := VariableHorizontalPanel
origin:0.0 @ 0.0 corner:1.0 @ 0.3
in:outerPanel.
"
the classListView is a scrollable SelectionInListView ...
"
scrollHelper := HVScrollableView
for:SelectionInListView
miniScrollerH:true
origin:0.0 @0.0 corner:0.5 @ 1.0
in:upperPanel.
classListView := scrollHelper scrolledView.
"
the methodListView also ...
"
scrollHelper := HVScrollableView
for:SelectionInListView
miniScrollerH:true
origin:0.5 @0.0 corner:1.0 @ 1.0
in:upperPanel.
methodListView := scrollHelper scrolledView.
"
the codeView is a scrollable CodeView ...
"
scrollHelper := HVScrollableView
for:CodeView
miniScrollerH:true
origin:0.0 @0.3 corner:1.0 @ 1.0
in:outerPanel.
codeView := scrollHelper scrolledView.
So far, so good; with this code, all view setup is finished.
(once the window builder is finished & released, you will be able to
create the above by painting the user interface ...
... however, its usually the smallest part of the application anyway.
The real challenge comes when we have to define the interaction.)
We can give it a first try with:
(of course, all views are empty. But the layout should be ok.
Try resizing the view or to change the relative sizes.)
SimpleBrowser open
There are two possible mechanisms to use:
#list:
message),
and asking the listView for a (callback) notification when
the selection changes.
The second approach is more flexible, if more views
(or in general: observers) are involved:
Consider the case that some other object is interested in a selection change
(for example, to give some hint in an explainer view, to automatically
search for senders/implementers etc.). With the first approach, you now
have a problem since your callback has to know about all others interested
in the selection and forward that information. Again, all those forwarding
has to be hardcoded in our application - i.e. the application has to know
about those others. This is probably not a very object oriented setup.
With the second approach, the other observer simply defines itself as another dependent of the model - and will automatically get the notification. Our application needs no knowledge whatsoever about those others.
(you will find background information in this in some text about the observer pattern)
Smalltalk/X supports both mechanisms, since there are applications where a simple action callback is easier to write down and the flexibility is not needed (simple buttons, for example).
Enough background explanation - we will use the model setup here.
Lets add code, to create the required models, arrange for a notification to
be sent to our application upon change, and give them
to the listViews.
Since we will have to have access to those models from
multiple parts of our application (and we have to keep them for the lifetime of
our view), we keep references to them in instance variables.
Therefore, we start by changing the classes definition to:
ApplicationModel subclass:#SimpleBrowser
instanceVariableNames:'classListModel
methodListModel'
classVariableNames:''
poolDictionaries:''
category:'Demos-Tutorial'
#setupSubviewsIn:
-method,
by adding code to create the models. The natural model for a SelectionInListView
is an instance of SelectionInList
. Those internally keep
the list (i.e. the items) and the index of the selected item.
Their dependents are notified when either changes.
...
"
create the selectionInList models and stuff them into the listViews
"
classListModel := SelectionInList new.
methodListModel := SelectionInList new.
classListView model:classListModel.
methodListView model:methodListModel.
...
and tell those models, to notify us (the application) whenever something
changes. There are basically two ways to be notified:
#update:...
message
#addDependent:
,
we will be notified via an #update:with:from:
message.
#onChangeSend:to:
,
and we can pass it the name of the message we can to be sent later.
...
"
let me know if something changes
"
classListModel onChangeSend:#classListChange to:self.
methodListModel onChangeSend:#methodListChange to:self.
...
[For the curious]:
The alternative code using a general update is:
...
classListModel addDependent:self.
methodListModel addDependent:self.
...
and the update method would be:
update:someAspect with:someArgument from:changedObject
changedObject == classListModel ifTrue:[
self classListChange
] ifFalse:[
changedObject == methodListModel ifTrue:[
self methodListChange
]
]
[end curiosity]
The two methods which get called on change are defined as dummies for now.
We will later come back and fill them with useful code:
and:
classListChange
"dummy for now"
The models which we used above are instances of
methodListChange
"dummy for now"
SelectionInList
.
These keep track a list and a selection index and will send out
change notifications when EITHER the list changes OR the
selection changes.
Actually, we are only interested in selection changes;
so we better setup things to only get selection change messages.
This can be done by accessing the models underlying selectionHolder:
(actually a SelectionInList object has no such thing physically -
it really keeps the selections index internally.
However, when asked for a selectionHolder, it return something which
simulates a model holding the selection instead of the index.
Since we think truly object oriented, we can ignore this fact
and don't care about that here ;-)
Without this, our change methods would be called even when
the lists are changed - and the change methods had to figure out if
it was a list change or a selection change (remember this for your
next project - it is sometimes useful, too).
...
"
let me know if something changes
"
classListModel selectionHolder onChangeSend:#classListChange to:self.
methodListModel selectionHolder onChangeSend:#methodListChange to:self.
...
updateClassList
"update the classList"
|collectionOfClasses collectionOfNames|
"
get classes ...
- since `Smalltalk allClasses' may return an unordered collection,
convert it into something which is orderer.
That is needed to allow sorting later.
`Smalltalk allClasses' only promises to return 'some'
collection (actually it returns a Set).
"
collectionOfClasses := Smalltalk allClasses asOrderedCollection.
"
collect names ...
"
collectionOfNames := collectionOfClasses
collect:[:aClass | aClass name].
"
sort them ...
"
collectionOfNames := collectionOfNames sort.
"
and pass them to the classListModel
"
classListModel list:collectionOfNames
of course, you can write things more compact (;-):
updateClassList
"update the classList"
classListModel
list:(Smalltalk allClasses asOrderedCollection
collect:[:aClass | aClass name]) sort
Now the big question: when do we have to call for this method ?
Smalltalk
sends change notifications,
whenever classes are added or removed. Therefore, we add our application
as a dependent of Smalltalk
(in the setup method)
and get automatic update for free:
...
Smalltalk onChangeSend:#updateClassList to:self
...
finally, we need an explicit send to the update method
when the application is started. Lets add the
follwoing to our openInterface
method:
...
self updateClassList.
...
Its now probably enough additional code in the setupSubviewsIn:
method, to justify its separation into two methods;
we extract the whole model setup stuff into an extra method:
setupSubviewsIn:topView
"create the panels with the selectionInListViews and the codeView"
|outerPanel upperPanel
classListView methodListView codeView
scrollHelper|
"
create the panels ...
"
outerPanel := VariableVerticalPanel
origin:0.0 @ 0.0 corner:1.0 @ 1.0
in:topView.
upperPanel := VariableHorizontalPanel
origin:0.0 @ 0.0 corner:1.0 @ 0.3
in:outerPanel.
"
the classListView is a scrollable SelectionInListView ...
"
scrollHelper := HVScrollableView
for:SelectionInListView
miniScrollerH:true
origin:0.0 @0.0 corner:0.5 @ 1.0
in:upperPanel.
classListView := scrollHelper scrolledView.
classListView model:classListModel.
"
the methodListView also ...
"
scrollHelper := HVScrollableView
for:SelectionInListView
miniScrollerH:true
origin:0.5 @0.0 corner:1.0 @ 1.0
in:upperPanel.
methodListView := scrollHelper scrolledView.
methodListView model:methodListModel.
"
the codeView is a scrollable CodeView ...
"
scrollHelper := HVScrollableView
for:CodeView
miniScrollerH:true
origin:0.0 @0.3 corner:1.0 @ 1.0
in:outerPanel.
codeView := scrollHelper scrolledView.
and another method which only cares for the models:
setupModels
"create the models"
"
create the selectionInList models and stuff them into the listViews
"
classListModel := SelectionInList new.
methodListModel := SelectionInList new.
"
let me know if something changes
"
classListModel selectionHolder onChangeSend:#classListChange to:self.
methodListModel selectionHolder onChangeSend:#methodListChange to:self.
"
let me know if classes are added or removed
"
Smalltalk onChangeSend:#updateClassList to:self.
"
update the classList now and here
"
self updateClassList.
finally, a call for setupModels
is added to openInterface
.
Of course, this has to be done before the views are setup (which expect
the models to be already created).
Now, reopen a new simpleBrowser to verify that the classes are shown correctly.
Of course, nothing happens in this browser when a class is selected.
SimpleBrowser open
ApplicationModel subclass:#SimpleBrowser
instanceVariableNames:'classListModel
methodListModel
currentClass
currentMethod'
classVariableNames:''
poolDictionaries:''
category:'Demos-Tutorial'
and modify our dummy #classListChange
method
(which already gets
called) to remember the class and update the methodList:
classListChange
"the class selection has changed"
|selection|
selection := classListModel selection.
selection notNil ifTrue:[
currentClass := Smalltalk classNamed:selection.
self updateMethodList
]
and define the methodList update method as:
updateMethodList
"update the methodList"
|dict selectors|
dict := currentClass methodDictionary.
selectors := OrderedCollection new.
dict keysAndValuesDo:[:selector :method | selectors add:selector].
methodListModel list:(selectors sort)
Again, reopen a new simpleBrowser to verify that things work correctly.
SimpleBrowser open
methodListChange
"the method selection has changed"
|selection|
selection := methodListModel selection.
selection notNil ifTrue:[
currentMethod := currentClass compiledMethodAt:selection asSymbol.
self updateCodeView
]
and add a method to show the methods source:
updateCodeView
"update the methodList"
codeView contents:(currentMethod source)
oops - we don't have codeView at hand here.
We either have to use a model again, or keep codeView in another instance
variable. Lets keep codeView in an instance variable:
ApplicationModel subclass:#SimpleBrowser
instanceVariableNames:'classListModel
methodListModel
currentClass
currentMethod
codeView'
classVariableNames:''
poolDictionaries:''
category:'Demos-Tutorial'
and remove the local variable with the same name in the #setupSubviewsIn:
method.
That completes most of our browser - we can now look at methods and
see the sourceCode.
SimpleBrowser open
To support this, codeViews provide a hook:
you can give it a block which it evaluates on accept.
(codeViews can also be used with a model - in that case, the model gets
an #accept notification).
The changes are straight forward: define the acceptAction when the
codeView is updated. This block will be evaluated by the codeView,
passing a collection of strings (the text lines of its contents) as argument.
Since the compiler expects a single string, we have to send #asString
to this string collection. Notice, that we cannot pass any useful method
category to the compiler - our browser is too dumb for that.
Be careful: only modify test-classes with this browser, since it
does not keep track of any method categories. All accepted methods
will get a dummy category - after all, this is only a simple demo.
updateCodeView
"update the methodList and set the acceptAction"
codeView contents:(currentMethod source).
codeView acceptAction:[:theCode |
currentClass
compile:theCode asString
classified:'no category'
]
To have something to play with, we should define some dummy classes.
#error:position:to:asWarning:
to highlight that text fragment). Luckily, codeViews do understand
those messages (thats what differenciates them from editTextViews),
so we simply pass it as the notifying:
argument to the
compiler:
codeView acceptAction:[:theCode |
Compiler
compile:theCode asString
forClass:currentClass
inCategory:'no category'
notifying:codeView.
]
this calls for the compiler directly - instead of asking the method
to recompile itself.
Also, we like some visible feedBack while compiling.
A good idea is to show a busy cursor during the compile:
The
codeView acceptAction:[:theCode |
codeView topView withCursor:(Cursor wait)
do:[
Compiler
compile:theCode asString
forClass:currentClass
inCategory:'no category'
notifying:codeView.
]
]
#withCursor:do:
evaluates a block and shows some cursor
in the meanwhile. If sent to a topView, it will change the cursor of all
its subviews (you could also change the codeViews cursor only).
Since we don't have the topView at hand
(could also be changed to an instance variable), the above code
simply asks the codeView for its topView. That works just as well.
#closeRequest
message to be sent to the application.
ApplicationModel
class
shuts down the topView. However, in out application we want
to check if the codeViews contents has been modified - and a confirmation
dialog should be displayed.
The code to do this is straight forward:
If the text has not been modified or the user confirms, we simply
call for the original
closeRequest
"sent if the topView is about to be closed"
codeView modified ifTrue:[
(self confirm:'text has not been accepted\\Close anyway ?' withCRs)
ifFalse:[
^ self
]
].
super closeRequest
closeRequest
s action to be performed
(which really closes the view).
To reset the modified status of the text, we should tell the codeView
in our acceptAction that its text is no longer considered being modified:
updateCodeView
"update the methodList and set the acceptAction"
codeView contents:(currentMethod source).
codeView acceptAction:[:theCode |
codeView topView withCursor:(Cursor wait)
do:[
Compiler
compile:theCode asString
forClass:currentClass
inCategory:''no category''
notifying:codeView.
codeView modified:false
]
]
As you may have already read in the tutorial, there are two possibilities
for menus: static menus and dynamic menus.
We will use a dynamic menu because those are typically the more flexible
choice.
Before we start to code, we need some background info on menus:
methodListView menuHolder:self;
menuMessage:#methodMenu.
methodListView menuPerformer:self.
#methodMenu
method, which has to return the popUpMenu:
methodMenu
"return a menu for the methodListView"
^ PopUpMenu
labels:#('senders' 'implementors')
selectors:#(#browseSenders #browseImplementors)
finally, we need corresponding methods to start the search:
browseSenders
"dummy for now"
and:
browseImplementors
"dummy for now"
You have to reopen a new demo-browser; the old one did not
execute the changed setup method.
SimpleBrowser open
To complete our project, here is the code to start browsing:
You should know your collection classes' protocol to understand
what is going on below ...
... agian, notice that we show a wait cursor - the search can take
a second or two.
and:
browseSenders
"ask for a selector and open a browser on its senders"
|selector senders|
selector := Dialog request:'selector:'.
self withCursor:(Cursor wait) do:[
selector isNil ifTrue:[^ self]. "/ cancel pressed
selector := selector asSymbol. "/ got a string - need a symbol
senders := OrderedCollection new.
Smalltalk allClasses do:[:aClass |
aClass methodDictionary do:[:aMethod |
(aMethod sends:selector) ifTrue:[
senders add:aMethod
]
]
].
senders isEmpty ifTrue:[
self information:'no senders'
] ifFalse:[
SystemBrowser
browseMethods:senders
title:('senders of ' , selector)
]
]
browseImplementors
"ask for a selector and open a browser on its implementors"
|selector classes implementors|
selector := Dialog request:'selector:'.
self withCursor:(Cursor wait) do:[
selector isNil ifTrue:[^ self]. "/ cancel pressed
selector := selector asSymbol. "/ got a string - need a symbol
classes := Smalltalk allClasses
select:[:aClass | aClass implements:selector].
implementors := classes
collect:[:aClass | aClass compiledMethodAt:selector].
implementors isEmpty ifTrue:[
self information:'no implementors'
] ifFalse:[
SystemBrowser
browseMethods:implementors
title:('implementors of ' , selector)
]
]
In Smalltalk/X, every subclass of View
and
ApplicationModel
inherits the resource file handling,
and the classes initialization methods read the resourceFiles during
startup or when the language is changed (for example, in the launcher).
Thus, all we have to do is to use translated strings, wherever language specific strings are present; mostly, these are window titles, menu strings and button labels.
For a description of how resources are accessed and how resourceFiles work, see Information for application programmers.
To use resources, replace:
with:
'someString'
and:
(resources string:'someString')
with:
#('string1' 'string2' ... 'stringN')
If a message or string involves a variables printString, as in:
(resources array:#('string1' 'string2' ... 'stringN') )
replace it with:
'there are ' , n printString , ' methods'
(notice, that this expansion automatically sends
(resources string:'there are %1 methods' with:n)
#printString
to
the item - so the code using resources may be even easier to read and
understand).
Be aware, that a sentences order may be different in other languages;
so you better translate the full sentence with all arguments instead of
concatenating individually translated parts.
As an example, the english:
is to be translated into german:
'do you really want to delete <fileName>'
which is impossible, if you create messages by concatenation:
'wollen Sie <fileName> wirklich löschen'
So, better translate it via:
(resources string:'do you really want to delete ') , fileName
and provide a translation in the resourceFile:
(resources string:'do you really want to delete %1') with: fileName
'do you really want to delete %1 ?' 'wollen Sie %1 wirklich löschen ?'
Using resources, our menu method becomes:
and the dialogBox opening becomes:
methodMenu
"return a menu for the methodListView"
^ PopUpMenu
labels:(resources array:#('senders' 'implementors'))
selectors:#(#browseSenders #browseImplementors)
...
selector := Dialog request:(resources string:'selector:').
...
"clients/Demos/SimpleBrowser.st"
.
'From Smalltalk/X, Version:2.10.9 on 20-apr-1996 at 3:47:32 pm' !
ApplicationModel subclass:#SimpleBrowser
instanceVariableNames:'classListModel methodListModel currentClass currentMethod
codeView'
classVariableNames:''
poolDictionaries:''
category:'Demos-Tutorial'
!
!SimpleBrowser class methodsFor:'documentation'!
documentation
"
this is a simple browser example
It can show the list of classes in the system,
and show a selected classes methods. If a method is
selected, the code is shown in the lower codeView.
Changed methods can be accepted and are recompiled.
The methodList provides a popupMenu for senders and
implementors.
"
! !
!SimpleBrowser methodsFor:'change notifications'!
classListChange
"the class selection has changed"
|selection|
selection := classListModel selection.
selection notNil ifTrue:[
currentClass := Smalltalk classNamed:selection.
self updateMethodList
]
!
methodListChange
"the method selection has changed"
|selection|
selection := methodListModel selection.
selection notNil ifTrue:[
currentMethod := currentClass compiledMethodAt:selection asSymbol.
self updateCodeView
]
! !
!SimpleBrowser methodsFor:'method menu'!
browseImplementors
"ask for a selector and open a browser on its implementors"
|selector classes implementors|
selector := Dialog request:(resources string:'selector:').
self withCursor:(Cursor wait) do:[
selector isNil ifTrue:[^ self]. "/ cancel pressed
selector := selector asSymbol. "/ got a string - need a symbol
classes := Smalltalk allClasses
select:[:aClass | aClass implements:selector].
implementors := classes
collect:[:aClass | aClass compiledMethodAt:selector].
implementors isEmpty ifTrue:[
self information:(resources string:'no implementors')
] ifFalse:[
SystemBrowser
browseMethods:implementors
title:((resources string:'implementors of %1' with:selector))
]
]
!
browseSenders
"ask for a selector and open a browser on its senders"
|selector senders|
selector := Dialog request:(resources string:'selector:').
self withCursor:(Cursor wait) do:[
selector isNil ifTrue:[^ self]. "/ cancel pressed
selector := selector asSymbol. "/ got a string - need a symbol
senders := OrderedCollection new.
Smalltalk allClasses do:[:aClass |
aClass methodDictionary do:[:aMethod |
(aMethod sends:selector) ifTrue:[
senders add:aMethod
]
]
].
senders isEmpty ifTrue:[
self information:(resources string:'no senders')
] ifFalse:[
SystemBrowser
browseMethods:senders
title:((resources string:'senders of %1' with:selector))
]
]
!
methodMenu
"return a menu for the methodListView"
^ PopUpMenu
labels:(resources array:#('senders' 'implementors'))
selectors:#(#browseSenders #browseImplementors)
! !
!SimpleBrowser methodsFor:'setup - models'!
setupModels
"create the models"
"
create the selectionInList models and stuff them into the listViews
"
classListModel := SelectionInList new.
methodListModel := SelectionInList new.
"
let me know if something changes
"
classListModel selectionHolder onChangeSend:#classListChange to:self.
methodListModel selectionHolder onChangeSend:#methodListChange to:self.
"
let me know if classes are added or removed
"
Smalltalk onChangeSend:#updateClassList to:self.
"
update the classList now and here
"
self updateClassList.
! !
!SimpleBrowser methodsFor:'setup - subviews'!
setupSubviewsIn:topView
"create the panels with the selectionInListViews and the codeView"
|outerPanel upperPanel
classListView methodListView scrollHelper|
"
create the panels ...
"
outerPanel := VariableVerticalPanel
origin:0.0 @ 0.0 corner:1.0 @ 1.0
in:topView.
upperPanel := VariableHorizontalPanel
origin:0.0 @ 0.0 corner:1.0 @ 0.3
in:outerPanel.
"
the classListView is a scrollable SelectionInListView ...
"
scrollHelper := HVScrollableView
for:SelectionInListView
miniScrollerH:true
origin:0.0 @0.0 corner:0.5 @ 1.0
in:upperPanel.
classListView := scrollHelper scrolledView.
classListView model:classListModel.
"
the methodListView also ...
"
scrollHelper := HVScrollableView
for:SelectionInListView
miniScrollerH:true
origin:0.5 @0.0 corner:1.0 @ 1.0
in:upperPanel.
methodListView := scrollHelper scrolledView.
methodListView model:methodListModel.
"
setup for its menu ...
"
methodListView menuHolder:self;
menuMessage:#methodMenu;
menuPerformer:self.
"
the codeView is a scrollable CodeView ...
"
scrollHelper := HVScrollableView
for:CodeView
miniScrollerH:true
origin:0.0 @0.3 corner:1.0 @ 1.0
in:outerPanel.
codeView := scrollHelper scrolledView.
! !
!SimpleBrowser methodsFor:'startup'!
openInterface
"sent by my superclass to open up my interface
(i.e. SimpleBrowser - openInterface)"
|topView|
topView := StandardSystemView new.
topView label:(resources string:'Simple Browser').
topView application:self.
self setupModels.
self setupSubviewsIn:topView.
topView open.
"
SimpleBrowser open
"
! !
!SimpleBrowser methodsFor:'updating'!
updateClassList
"update the classList"
classListModel
list:(Smalltalk allClasses asOrderedCollection
collect:[:aClass | aClass name]) sort
!
updateCodeView
"update the methodList and set the acceptAction"
codeView contents:(currentMethod source).
codeView acceptAction:[:theCode |
codeView topView withCursor:(Cursor wait)
do:[
Compiler
compile:theCode asString
forClass:currentClass
inCategory:'no category'
notifying:codeView.
]
]
!
updateMethodList
"update the methodList"
|dict selectors|
dict := currentClass methodDictionary.
selectors := OrderedCollection new.
dict keysAndValuesDo:[:selector :method | selectors add:selector].
methodListModel list:(selectors sort)
! !
And, an example resource file "SimpleBrowser.rs
",
containing german and french translations.
;
; SimpleBrowser.rs
;
#if Language == #german
'Simple Browser' 'Einfacher Browser'
'senders' 'Sender'
'implementors' 'Implementierungen'
'senders of %1' 'Sender von %1'
'no senders' 'keine Sender'
'selector:' 'Selektor:'
'implementors of %1' 'Implementierungen von %1'
'no implementors' 'keine Implementierung'
#endif
;
; (well, my french is too bad ... please excuse)
;
#if Language == #french
'Simple Browser' 'guestionnaire simple'
'senders' 'émetteurs'
'implementors' 'réalisations'
'senders of %1' 'émetteurs de %1'
'no senders' 'pas des émetteurs'
'selector:' 'selecteur:'
'implementors of %1' 'réalisations de %1'
'no implementors' 'pas des réalisations'
#endif
Copyright © 1995 Claus Gittinger, all rights reserved
<cg at exept.de>