[prev] [up] [next]

A Complete Programming Example

Contents

Motivation


Attention:
This example does not make use of the new GUI Painter Tools.
A corresponding example using the GUI Painter will be added soon ...
This document shows how a typical (simple) browser application can be implemented. Its code may be useful as a framework to start with for other browser-like applications.

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 ....

Definition of the programming task

The browser application is meant as an example, with some functionality of the real class browser, but stripped down heavily to keep the example simple.

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.

Setting up the browser's window

Lets start by defining the browser's class and a method to setup the topView with its subviews.
In order to reuse existing code as much as possible, lets define the browser as a subclass of ApplicationModel, which provides a framework for this type of application:
    ApplicationModel subclass:#SimpleBrowser
	    instanceVariableNames:''
	    classVariableNames:''
	    poolDictionaries:''
	    category:'Demos-Tutorial'
Press: to add the above definition to your system and open a browser on it.

ApplicationModel expects that its subclasses implement a method named openInterface to create and setup the application's window.
Lets define it as:

    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
	"
Explanations:

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:

    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.
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 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.
Press: to add the above methods to the SimpleBrowser class.
Explanations:

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:

    SimpleBrowser open
(of course, all views are empty. But the layout should be ok. Try resizing the view or to change the relative sizes.)

Setting up models

Our next task is to fill the listViews with some information. For the classList, we have to ask the system for a list of all classes in the system, generate a collection of names, and pass it to the classList.
Once a selection is made, the same has to be done for methods found in the selected class and the list must be passed to the methodListView.

There are two possible mechanisms to use:

If you are used to Xtoolkit or Windows programming, the first approach may be more natural to you - the set up is somewhat easier to understand, but less flexible (as we will see in a minute).

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'
Press: to change the definition in your system.
Then, we change the #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: In our application, the second mechanism is easier to use:
    ...

    "
     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:

    classListChange
	"dummy for now"
and:
    methodListChange
	"dummy for now"
The models which we used above are instances of 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 ;-)
    ...

    "
     let me know if something changes
    "
    classListModel selectionHolder onChangeSend:#classListChange to:self.
    methodListModel selectionHolder onChangeSend:#methodListChange to:self.
    ...
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).
Press: to change the method in your system.

Getting the classList

We are now ready to show the list of class names in the classListView.
First, ask the system for a collection of all classes, then collect their nameStrings and finally, give this string collection to the classListModel.
We perform this in an extra method, called updateClassList:
    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 ?
Definitely, when our browser starts up.
However, it would be nice to update the list also whenever a class has been added or removed by some other browser.
We are lucky, since 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:
(mhmh - should have done this right away ...):
    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).
Press: to add the above changes to your system.
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

Getting the methodList

Next, we have to show the selected classes methods, whenever the classSelection changes.
For that, it is useful to keep references to the currently selected class and method in instance variables; therefore, we change the classes definition to:
    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)
Press: to add the above changes to your system.
Again, reopen a new simpleBrowser to verify that things work correctly.
    SimpleBrowser open

Getting the methods source

Finally, the same procedure for the methods sourceCode:
    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.
Press: to add the above changes to your system.
That completes most of our browser - we can now look at methods and see the sourceCode.
Try it again:
    SimpleBrowser open

Accepting changed methods

The next thing that is required is some accept mechanism: the selected method should be recompiled when the codeView accepts (either via its menu, or by a shortkey).

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.

    updateCodeView
	"update the methodList and set the acceptAction"

	codeView contents:(currentMethod source).
	codeView acceptAction:[:theCode |
			currentClass
				compile:theCode asString
				classified:'no category'
		]
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.
To have something to play with, we should define some dummy classes.
Press: to add the above change and some dummy classes to your system.

Little bells & whistles

If you try to accept a faulty method in the above browser, no error highlighting appears. The reason is simply that the compiler does not know about the codeView. The compiler is prepared to forward any error or warning messages to some object (which has to implement messages like #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:

	codeView acceptAction:[:theCode |
	    codeView topView withCursor:(Cursor wait)
	    do:[
		Compiler
		    compile:theCode asString
		    forClass:currentClass
		    inCategory:'no category'
		    notifying:codeView.
	     ]
	]
The #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.

Intercepting window close

As described above, telling the topView about the application object allows it to intercept various window events, and decide locally how things are to be handled. For example, the closing of the topView (via the windowManager) will result in a #closeRequest message to be sent to the application.
The default implementation (in the 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:

    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
If the text has not been modified or the user confirms, we simply call for the original closeRequests 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
	 ]
    ]
Press: for these changes.

Adding a PopupMenu

As a final addition, lets add a popupMenu to the methodList. Here, we would like to be able to search for senders and implementors of some message.

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:

So we add the above to our view setup and add the #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"
Press: for these changes.
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:
(to keep things simple, we open up standard browsers here ... ... to not make things too simple, we search for senders/implementors here - although there are standard startup messages for this found in the SystemBrowser class)

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.

    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)
	    ]
	]
and:
    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)
	    ]
	]
Press: for these changes to be installed.
While performing the above tutorial, some classes were added to the system; press Press: to remove them:

Prepare for international use

You should write all of your applications to be prepared for use with other languages - i.e. for all strings to come from the resource files instead of hardcoding them.
Although we do this here as a final step, you better get used to writing the code for internationalization right from the start.

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:

	'someString'
with:
	(resources string:'someString')
and:
	#('string1' 'string2' ... 'stringN')
with:
	(resources array:#('string1' 'string2' ... 'stringN') )
If a message or string involves a variables printString, as in:
	'there are ' , n printString , ' methods'
replace it with:
	(resources string:'there are %1 methods' with:n)
(notice, that this expansion automatically sends #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:

    'do you really want to delete <fileName>'
is to be translated into german:
    'wollen Sie <fileName> wirklich löschen'
which is impossible, if you create messages by concatenation:
    (resources string:'do you really want to delete ') , fileName
So, better translate it via:
    (resources string:'do you really want to delete %1') with: fileName
and provide a translation in the resourceFile:
    'do you really want to delete %1 ?'  'wollen Sie %1 wirklich löschen ?'

Using resources, our menu method becomes:

    methodMenu
	"return a menu for the methodListView"

	^ PopUpMenu
		labels:(resources array:#('senders' 'implementors'))
		selectors:#(#browseSenders #browseImplementors)
and the dialogBox opening becomes:
    ...
    selector := Dialog request:(resources string:'selector:').
    ...

The final code

For all of you, who are reading this on paper or an external html reader, here is the final code, en-bloque, in fileOut format.
You will also find it in the file: "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.
You can add more languages at any time - there is no need to recompile or even edit the code.
;
; 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>

Doc $Revision: 1.28 $ $Date: 2021/03/13 18:24:52 $