Background ---------- Squeak is a Smalltalk implementation. The standard "full" image includes an IRC client (runnable with "IRCConnection new openView" or by hitting Escape, selecting "open..." from the "World" menu, and then selecting "IRC chat" from the "open..." menu), which had a "new connection" window that was absurdly sized on my machine. So I fixed it. At the end of this mail I have attached my patch in the form of a change-set. Save it in a file with a ".cs" extension. Then, if you open a "file list window" ("open..." "file list") and click on a file containing this change-set, you should see a "fileIn" button appear next to the usual "name", "date", and "size" button. Clicking on it loads the code into your running Squeak image.
Running notes on making these changes ------------------------------------- This is a partly fictionalized account of how I went about exploring some completely unfamiliar code early in my exploration of the Squeak image. I hope it conveys some of the sense of the environment. You may wish to play along, doing the steps I went through --- but don't follow the "putting a self halt into the extent:" method if you can help it. It's fictionalized because I started it rather late in the process of the exploration, so I'd already forgotten a lot of the things I'd done --- especially the mistakes --- and sometimes I didn't notice new mistakes as I was making them. I went back and redid things to see bits I had forgotten. I brought up an IRC client: "IRCConnection new openView" which was nice but wasn't connected to a server. The little menu icon at the top of the scrollbar offered me some help here; there was a 'connect' item on the menu. It brought up a usable dialog box, but the dialog was absurdly large; 400x150 would have been fine, and it was 600x400. I wanted to know why. I inspected the 'connect' morph (right click on the menu item until there was a halo around just the menu item, then select the white wrench, then select "inspect morph") and found that it had a property called "selector", with value #perform:orSendTo:, and a property called "arguments", with value #(#openConnectionDialogue a PluggableTextMorph(1309)). That looked like it might be the method being called by that menu item. So I double-clicked 'openConnectionDialogue' to highlight it and hit alt-b to browse the methods by that name, and found a list of one, in IRCConnection. I clicked on that method to see its source code, which was a little under fifty lines long; it consisted of creating a SystemWindow called "dialogue", adding a bunch of crap to it, and finally calling "dialogue openInWorld." So I right-clicked on the actual dialog window, selected the wrench, selected "inspect morph", and lo and behold, the inspector window said the dialog window was a SystemWindow. It had a property named "bounds" which seemed to have the size and position of the window in it --- it changed when I resized the window by hand. So I set out to see who was setting "bounds" to such a large value. Experimenting in the inspector showed that typing "self openInWorld" and hitting alt-d ("do it") caused it to get blown way up again. I browsed the class (clicked on "self" in the inspector and hit alt-b) to find out who was setting "bounds". I clicked the "inst vars" button in the browser and selected "bounds" from the drop-down menu to get a list of methods; unfortunately most of them only read "bounds" and didn't set it. It turns out that the inspector window has a menu item to see the "methods storing into this inst var" which is what I should have used. There were several candidates: - Morph initialize and Morph basicInitialize set the bounds to "self defaultBounds", but "self defaultBounds" on the SystemWindow returns 200x100, not 600x400. I know this because I can type "self defaultBounds" in the inspector window and hit alt-p. - Morph extent: sets the size of the window to whatever point you pass in. Hard to tell who's calling that. - there's a corresponding Morph position: method to set the position. - MorphicModel recomputeBounds does some stuff I don't understand, but running "self recomputeBounds" makes the window really small, not 600x400. I had a "self extent: [EMAIL PROTECTED]" in my inspector; whenever I screwed up the size of the window, I just arrowed up to it and hit alt-d to fix the window. Nothing else looked very promising; it all seemed to be related to moving or cloning or embedding windows, none of which were happening here. I figured it was probably somebody calling #extent:, but #extent: had a lot of senders. (I could click on extent: and hit alt-n to see all nine hundred of them.) There were even a dozen or so inside SystemWindow. I tried putting a "self halt." into Morph extent:, (I could edit it right from the list of methods that stored into bounds, then hit alt-s to make the change take effect) but that didn't work out so well; Squeak ran out of memory and crashed after a minute or so. Presumably the act of trying to pop up a walkback window was hitting the self halt, which was trying to pop up another walkback window, etc. I restarted Squeak. So I took a different tack: I looked at my SystemWindow browser to see what happens when you openInWorld. Except SystemWindow doesn't implement openInWorld, so I had to double-click on "openInWorld" in my inspector window and use alt-m to see who implements it. Morph openInWorld looked like it was probably going to try to call #openInWorld: (the inspector told me that "self couldOpenInMorphic" was "true" when I typed it and hit alt-p) so I doubleclicked "openInWorld:" and hit alt-m to see who implemented that. Turns out SystemWindow has an openInWorld: method, so I clicked it. Lo and behold, it calls a method called #bounds: with the argument (RealEstateAgent initialFrameFor: self world: aWorld). I guessed that #bounds: was a method to set the bounds, and that the RealEstateAgent expression was returning [EMAIL PROTECTED] Unfortunately, I couldn't conveniently evaluate that expression in place to see if it was in fact returning [EMAIL PROTECTED], since it involved aWorld. So I double-clicked "initialFrameFor:" and used alt-shift-W to try to find the method. (I couldn't use alt-m because the method was called #initialFrameFor:world:, not #initialFrameFor:.) This brought up a list of message names containing "initialframefor:", of which one was #initialFrameFor:world:, and it was implemented only by the RealEstateAgent class. It merely delegated to another method, #initialFrameFor:initialExtent:world, but the argument following "initialExtent:" was "aView initialExtent". Now, aView in this case was my SystemWindow, so I typed "self initialExtent" and hit alt-p in my SystemWindow inspector to see if it was the one returning [EMAIL PROTECTED] Sure enough, it was. Now it remained only to find out why self initialExtent was returning such a silly size, and to fix it. My first attempt was typing "self initialExtent: [EMAIL PROTECTED]" and hitting alt-d, but that just told me that SystemWindow didn't understand the message #initialExtent. So I did what I should have done earlier with openInWorld: I typed "self initialExtent" and selected "debug it" from the middle-click menu. After clicking the debugger's "Send" button a couple of times, I had this stack trace visible: RealEstateAgent class>>standardWindowExtent UndefinedObject(Object)>>initialExtent SystemWindow>>initialExtent It turned out that SystemWindow>>initialExtent simply answered "model initialExtent", and model was nil, so it inherited the default Object implementation of initialExtent, which simply answered "RealEstateAgent standardWindowExtent". At this point it became apparent that I didn't need to dig in any deeper, because I didn't really want to change RealEstateAgent standardWindowExtent to return a size suitable for this little dialog box. So I clicked "Proceed" to close the debugger window. Now, I had two things I could do: I could either give the "connect to an IRC server" SystemWindow a "model", one whose initialExtent would return the right thing, or I could make an "IRCConnectDialogue" class that would have an override for #initialExtent and maybe also break up the code in the openConnectionDialogue method into method-sized chunks. Or, as a third alternative, I could give SystemWindow an instance variable that would be its return value for initialExtent if it didn't have a model, but that didn't sit as well with me. So I created a new class called IRCConnectionDialogue. I clicked on a class in the right category in the System Browser to see the class definition, then edit it until it looks the way you want, then hit alt-s --- I made the superclass be SystemWindow and gave it no instance variables) and I copied and pasted the text of the openConnectionDialogue method into the class and renamed it #initialize. Then I had second thoughts and renamed it openInWorld. I removed the temp variable 'dialogue' from the list of temp variables at the top of the method and replaced it with 'self' throughout the function, except at the bottom where I called 'super openInWorld.' Then I hit alt-s, which told me about the places I'd forgotten and was still using 'dialogue', so I fixed those. (Find and replace goes like this: double-click "dialogue", hit alt-h, type "self", then hit alt-j to find the next "dialogue" and replace it with "self", and repeat until done.) Now I had two methods, initialize and openInWorld. So I clicked "initialize" in the method list and selected "remove method (x)" from the menu. It asked me if I really wanted to remove a method with 609 senders. I said yes, and it took a while, and I panicked a bit, thinking I had just removed all of the implementations of "initialize" --- which wouldn't leave a very functional Squeak. But quick browsing showed that other classes still had an "initialize" method. Now, there were a bunch of references in openConnectionDialogue to either instance variables of IRCConnection or "self". On closer inspection, the things that looked like instance variables were actually inside of a #(literal array), and to my surprise, when I typed "#(a b c d)" into my workspace and did alt-p, I got back "#(#a #b #c #d)" --- so those apparent instance variables were actually symbols, which could be used as message names or something. Good thing, because there's no "portAsString" instance variable of IRCConnection, although there is a portAsString method. So anyway, the references to self mostly have to do with what text morphs and buttons do. So I decided I should put all this stuff in yet a third class, since the textboxes and buttons clearly need access to the IRCConnection, and require you to do something like "(IRCConnectionDialogue new for: self) openInWorld" in IRCConnection>>openConnectionDialogue. Double-clicking on "for:" and hitting alt-m shows me that there's only one implementor of the method #for:, and it's in some class called RandomEnvelope, so I don't think I have to worry about a method-name conflict. So I rename my new openInWorld method to "for: aConnection", replace the "self"s in it that used to refer to the IRCConnection with "aConnection", remove my "openInWorld" method (click, alt-x, confirm), and then remove the "super openInWorld" call from the end of for:. Now I go back to IRCConnection and change its openConnectionDialogue method to do the above new for: crap, and then I try it out. It appears to work. (Although the IRC client is kind of hard to use.) Now to do what I came here to do in the first place: override initialExtent to return something reasonable. In the browser looking at IRCConnectionDialogue, I click on "as yet unclassified" in the message classification, and then click in the pane below to write an initialExtent method that just does "^ [EMAIL PROTECTED]", then hit alt-s to make it work. That seems to have worked. Now I'm going to refactor this code a little bit. There's a loop body that I think should be a method, and it appends to a temp variable list that I think should be an instance variable. In the process of attempting this refactoring, I accidentally deleted the body of the loop and didn't save it anywhere. But it wasn't a big deal, because Squeak keeps all the previous versions of the method around; I just clicked on the "versions" button, found the version before the one I'd screwed up in the "previous versions" window, then hit the "revert" button. A couple of times I accidentally created a class called NameOfSubclass and had to delete it. I turned a lot of temporary variables into instance variables while doing this. At one point, I wanted to move the initializations of these instance variables into something that happened once at instantiation time, before for: got called, rather than inside for: itself. In order to find out whether 'initialize' got called, and whether it was before for:, I defined an initialize method that just did "self inform: 'initialize called', (textEntryList asString)." Then when I popped open the dialog again, it told me when initialize was called, and it also told me what textEntryList was at the time -- nil. I also broke it, because I wasn't calling super initialize in my override of initialize. The debugger told me this because it was trying to iterate over subMorphs or something, and subMorphs was nil. So I added super initialize back in, and it worked. Pulling the body of the loop out into a method allowed me to use my inspector to add more fields to the connection dialog box simply by calling a method; unfortunately the layout stuff didn't seem to work out right. This is perhaps unsurprising because the original code was doing something that looked suspiciously like manually performing layout in a very static sort of way. That's the easy way to get grid layouts, I guess. The Change Set -------------- 'From Squeak3.6 of ''6 October 2003'' [latest update: #5429] on 16 June 2004 at 3:06:44 am'! "Change Set: IRC Dialog Resizing Date: 16 June 2004 Author: Kragen Sitaker I noticed that the IRC connection dialog box was a little over-large in my environment, so I thought I'd give it a different initialExtent. This was a little harder than it seemed, since I didn't know anything about Morphic or Squeak, but eventually I got it working --- after pulling out the connect dialogue box into its own class. I'm not delighted with the results --- although the dialogue box has a better size now, the layout is still pretty fragile. Making it unfragile seems to require a grid layout system, and I can't find one of those yet. "! SystemWindow subclass: #IRCConnectionDialogue instanceVariableNames: 'textEntryList y yDelta ' classVariableNames: '' poolDictionaries: '' category: 'Network-IRC Chat'! !IRCConnection methodsFor: 'UI' stamp: 'kjs 6/16/2004 01:48'! openConnectionDialogue "open a dialogue for making new connections" (IRCConnectionDialogue new for: self) openInWorld. ! ! !IRCConnectionDialogue methodsFor: 'as yet unclassified' stamp: 'kjs 6/16/2004 02:51'! addEditableFor: aConnection method: aSymbol labeled: aString | textEntry descMorph | descMorph _ PluggableButtonMorph on: aConnection getState: nil action: nil. descMorph hResizing: #spaceFill; vResizing: #spaceFill; label: aString. self addMorph: descMorph frame: ([EMAIL PROTECTED] extent: [EMAIL PROTECTED]). textEntry _ PluggableTextMorph on: aConnection text: aSymbol accept: (aSymbol, ':') asSymbol. textEntry extent: [EMAIL PROTECTED]; color: (Color r: 1.0 g: 1.0 b: 0.599); acceptOnCR: true. self addMorph: textEntry frame: ([EMAIL PROTECTED] extent: [EMAIL PROTECTED]). textEntryList add: textEntry. y _ y + yDelta. ! ! !IRCConnectionDialogue methodsFor: 'as yet unclassified' stamp: 'kjs 6/16/2004 02:35'! for: aConnection "open a dialogue for making new connections" | connectButton | self addEditableFor: aConnection method: #server labeled: 'server'; addEditableFor: aConnection method: #portAsString labeled: 'port'; addEditableFor: aConnection method: #nick labeled: 'nick'; addEditableFor: aConnection method: #userName labeled: 'username'; addEditableFor: aConnection method: #fullName labeled: 'full name'. connectButton _ PluggableButtonMorph on: [ textEntryList do: [ :m | m hasUnacceptedEdits ifTrue:[ m accept ] ]. aConnection connect ] fixTemps getState: nil action: #value. connectButton hResizing: #spaceFill; vResizing: #spaceFill; label: 'connect'. self addMorph: connectButton frame: ([EMAIL PROTECTED] extent: [EMAIL PROTECTED]). self setLabel: 'connect to an IRC server'. ! ! !IRCConnectionDialogue methodsFor: 'as yet unclassified' stamp: 'kjs 6/16/2004 01:56'! initialExtent [EMAIL PROTECTED] ! !IRCConnectionDialogue methodsFor: 'as yet unclassified' stamp: 'kjs 6/16/2004 02:38'! initialize y _ 0. yDelta _ 0.8 / 5. textEntryList _ OrderedCollection new. super initialize.! !