We left off last week with little more than a pretty picture that does nothing. This time, we'll add all the functionality to make our basic ClassHierarchyBrowser work, and while doing so, we'll steal some of the code from Vassili's work to make ours that much better.
To start, do come refactoring. (Note, I actually used the RefactoringBrowser to do this work, but since I'm not in the business here of explaining how that works, I'll just explain how to do it by hand). We use the same symbols over and over, such as #MethodList and #ProtocolList, so let's instead put them into their own accessing methods:
Now we'll go to #protocolAndMethodListForm, and replace them with the accessors... (Note: I put this stuff in the category "constants")... While we're at it, we'll make BOTH lists multi select, and tell the method list to use the display selector #selector:
This last bit about using the displaySelector: #selector is where Vassili's fine work comes in. By default, items in a list are displayed by sending the #displayString method to the item, and then showing the result. In this case, using Vassili's work, instead of filling our Method List with symbols representing the method selectors, we'll be filling our Method List with instances of MethodDefinition. This gives us more flexibility. MethodDefinitions answer the symbol representing the selector when sent the #selector method... so that's why we're setting the displaySelector to #selector.
Next, we'll add some accessing methods to access the various panes:
The code here evolved while I was writing it, via refactoring. In fact, it isn't very interesting in itself, but it does make the code cleaner.
Now we need to hook it all up! So, now is time to rewrite our #hookupInterface method that we commented out last time. We'll start with just hooking up the ClassTree to the protocol list(s):
hookupInterface
self classTree when: #selectionChanged send: #fillProtocolList to: self.
In turn, we have to modify the old #fillProtocolList method:
fillProtocolList
self classTree selection ifNotNil:
[:value | self instanceProtocolList list: value organization categories].
self classTree selection ifNotNil:
[:value | self classProtocolList list: value class organization categories]
Note here that what we are filling both the Class protocol list and the Instance protocol list. One thing you should be aware of is that even when a TabControl page is not active, the widgets on that pane are. This allows us to fill both protocol lists even though only one is visible, and that is exactly what the above does.
Next we'll hook up the two protocol lists in our hookupInterface method:
self classTree when: #selectionChanged send: #fillProtocolList to: self.
self instanceProtocolList
when: #selectionChanged send: #fillInstanceMethodList to: self;
when: #listChanged send: #fillInstanceMethodList to: self.
self classProtocolList
when: #selectionChanged send: #fillClassMethodList to: self;
when: #listChanged send: #fillClassMethodList to: self.
Here, we fill the appropriate method list (class or instance) when the list is changed (such as when a new class is chosen in the Class Tree) or when a new selection is made. So, we need to write a #fillInstanceMethodList and #fillClassMethodList method (and remove the old #fillMethodList that was hanging around):
You see here, we're filling the associated method list (again, class or instance) based on what selections are made in the associated protocol list. Here again we steal from Vassili, both using the #selectionsDo: method, that iterates over all selections, and filling the list with MethodDefinitions. Note also that one deals with the class, while the other deals with the class class. That's standard Smalltalk stuff, the Class holds instance definition values, while the Class's class owns the class definition values.
Next, we want to hook up the Text pane in our hookupInterface method and fill it with do the displayMethodSource method:
Again, we steal from Vassili to use #selectionDo: as well as #formattedSourceCode. In terms of the widgets, the most interesting thing is the code at the top of the #displayMethodSource. Here, we ask the TabControl, what is the active page and compare it to the constant we created for the Instance page... If it matches, we use the instanceMethodList as our source pane, otherwise we use the classMethodList. This makes sure that a new selection will always display the right thing, depending on which page is showing.
We're almost done, but not quite. Let's take the case where someone chooses a class, say Object, then chooses an instance protocol, and then a method, then clicks on the class page, and selects a class protocol and then a class method.
What happens when they then simply select the instance page? Currently, the class method text remains visible. We want to make it so that if there IS something to see when they change pages, that the method text is updated. We'll do that by adding a final hookup in our hookupInterface method:
when: #listChanged send: #displayMethodSource to: self.
self tabControl
when: #pageChangedTo: send: #displayMethodSource to: self.
The #pageChangedTo: event is fired when a page is changed. The trailing colon in the event name says that it can supply an argument. In this case, it supplies as an argument the page number of the newly visible page. We've written #displayMethodSource so that it doesn't need to have the page number passed in as a parameter, so we don't use the parameter, and when the event gets triggered, the page number is sent to the bit bucket.
The above is published as version 1.18 in the Package named "PollockBlog" on the Cincom public repository.
Well, that's it. Our ClassHierarchyBrowser is now more than just a pretty picture. It changes information in various panes as you select on stuff. It allows you to edit the method source, but it doesn't allow you to save it or anything... we'll leave that as an exercise for the reader with one caution: If you do it wrong, you can really screw stuff up.
Next time, we'll add a simple MenuBar to our ClassHierarchyBrowser.