scide: LookupDialog - redo lookup on classes after partial lookup
[supercollider.git] / SCClassLibrary / Common / GUI / PlusGUI / Core / ClassBrowser.sc
blob6e1d363c5dc26b653904fe6239d98d535deb645e
1 // A thorough refactoring of JMc's original class browser
2 // Previously a state was a Class or an array representing a search result
3 // Now it's an Environment holding more info
4 // James Harkins
6 ClassBrowser {
7         classvar        updateProtos, searchMenuKeys, viewList, buttonSet, gray;
9         var     <views,
10                         // historyPos == 0 means one item in history[0]
11                         // so it must be negative for empty history
12                 currentState, history, historyPos = -1,
13                 gui, hvBold12;
15         *new { arg class;
16                 ^super.new.init(class)
17         }
19                 // some basic getters
20                 // views that can change current class or method
21                 // should always keep the state environment variables up-to-date
22         current { ^currentState }
23         currentClass { ^currentState.currentClass }
24         currentMethod { ^currentState.currentMethod }
26         init { arg class;
27                 this.class.initGUI;
28                 gui = GUI.scheme;
29                 hvBold12 = Font.sansSerif( 12 ).boldVariant;
31                 history = [];
33                 views = Environment(parent: updateProtos, know: true).make {
35                         ~window = gui.window.new("class browser", Rect(128, (GUI.window.screenBounds.height - 638).clip(0, 320), 720, 600))
36                                 .onClose_({ this.free });
38                         ~window.view.decorator = FlowLayout(~window.view.bounds);
40                         ~currentClassNameView = gui.textField.new(~window, Rect(0,0, 308, 32));
41                         ~currentClassNameView.font = Font.sansSerif( 18 ).boldVariant;
42                         ~currentClassNameView.align = \center;
44                         ~currentClassNameView.action = {
45                                 this.makeState(views.currentClassNameView.string.asSymbol.asClass, \class);
46                         };
48                         ~superClassNameView = gui.staticText.new(~window, Rect(0,0, 256, 32));
49                         ~superClassNameView.font = hvBold12 = Font.sansSerif( 12 ).boldVariant;
51                         ~window.view.decorator.nextLine;
53                         ~bakButton = gui.button.new(~window, Rect(0,0, 24, 24));
54                         ~bakButton.states = [["<"]];
55                         ~bakButton.action = {
56                                 if (historyPos > 0) {
57                                         historyPos = historyPos - 1;
58                                         this.restoreHistory;
59                                 }
60                         };
62                         ~fwdButton = gui.button.new(~window, Rect(0,0, 24, 24));
63                         ~fwdButton.states = [[">"]];
64                         ~fwdButton.action = {
65                                 if (historyPos < (history.size - 1)) {
66                                         historyPos = historyPos + 1;
67                                         this.restoreHistory;
68                                 }
69                         };
72                         ~superButton = gui.button.new(~window, Rect(0,0, 50, 24));
73                         ~superButton.states = [["super"]];
75                         ~superButton.action = {
76                                 if(currentState.currentClass.notNil) {
77                                         this.makeState( currentState.currentClass.superclass );
78                                 }
79                         };
81                         ~metaButton = gui.button.new(~window, Rect(0,0, 50, 24));
82                         ~metaButton.states = [["meta"]];
84                         ~metaButton.action = {
85                                 if(currentState.currentClass.notNil) {
86                                         this.makeState( currentState.currentClass.class );
87                                 };
88                         };
91                         ~helpButton = gui.button.new(~window, Rect(0,0, 50, 24));
92                         ~helpButton.states = [["help"]];
94                         ~helpButton.action = {
95                                 if(currentState.currentClass.notNil) {
96                                         currentState.currentClass.help;
97                                 }
98                         };
100                         ~classSourceButton = gui.button.new(~window, Rect(0,0, 90, 24));
101                         ~classSourceButton.states = [["class source"]];
103                         ~classSourceButton.action = {
104                                 if(currentState.currentClass.notNil) {
105                                         currentState.currentClass.openCodeFile;
106                                 }
107                         };
109                         ~methodSourceButton = gui.button.new(~window, Rect(0,0, 90, 24));
110                         ~methodSourceButton.states = [["method source"]];
111                         ~methodSourceButton.action = {
112                                 if(currentState.currentMethod.notNil) {
113                                         currentState.currentMethod.openCodeFile;
114                                 };
115                         };
117                         ~implementationButton = gui.button.new(~window, Rect(0,0, 100, 24));
118                         ~implementationButton.states = [["implementations"]];
119                         ~implementationButton.action = {
120                                 if(currentState.currentMethod.notNil) {
121                                         thisProcess.interpreter.cmdLine = currentState.currentMethod.name.asString;
122                                         thisProcess.methodTemplates;
123                                 };
124                         };
126                         ~refsButton = gui.button.new(~window, Rect(0,0, 70, 24));
127                         ~refsButton.states = [["references"]];
128                         ~refsButton.action = {
129                                 if(currentState.currentMethod.notNil) {
130                                         thisProcess.interpreter.cmdLine = currentState.currentMethod.name.asString;
131                                         thisProcess.methodReferences;
132                                 };
133                         };
135                         if(this.respondsTo(\openSVN)) {
136                                 ~svnButton = gui.button.new(~window, Rect(0,0, 32, 24));
137                                 ~svnButton.states = [["svn"]];
138                                 ~svnButton.action = {
139                                         var filename, svnAddr;
140                                         if(currentState.currentMethod.notNil) {
141                                                 svnAddr = "http://supercollider.svn.sourceforge.net/viewvc/supercollider/trunk/common/build/";
142                                                 filename = currentState.currentClass.filenameSymbol.asString;
143                                                 svnAddr = svnAddr ++ filename.drop(filename.find("SCClassLibrary"));
144                                                 svnAddr = svnAddr ++ "?view=log";
145                                                 this.openSVN( svnAddr );
146                                         };
147                                 };
148                         };
150                         ~window.view.decorator.nextLine;
152                         GUI.staticText.new(~window, Rect(0, 0, 65, 20)).string_("Search for");
153                         ~searchField = GUI.textField.new(~window, Rect(0, 0, 235, 20))
154                                 .action_({
155                                         this.searchClasses(views.searchField.string,
156                                                 views.searchMenu.value, views.matchCaseButton.value);
157                                 });
158                         GUI.staticText.new(~window, Rect(0, 0, 15, 20)).string_("in").align_(\center);
159                         ~searchMenu = GUI.popUpMenu.new(~window, Rect(0, 0, 200, 20));
160                         ~matchCaseButton = GUI.button.new(~window, Rect(0, 0, 100, 20))
161                                 .states_([["Case insensitive"], ["Match case"]]);
163                         ~searchButton = GUI.button.new(~window, Rect(0, 0, 40, 20))
164                                 .states_([["GO"]])
165                                 .action_({
166                                         this.searchClasses(views.searchField.string,
167                                                 views.searchMenu.value, views.matchCaseButton.value);
168                                 });
170                         ~window.view.decorator.nextLine;
172                         ~filenameView = gui.staticText.new(~window, Rect(0,0, 600, 18));
173                         ~filenameView.font = Font.sansSerif( 10 );
175                         ~window.view.decorator.nextLine;
176                         gui.staticText.new(~window, Rect(0,0, 180, 24))
177                                 .font_(hvBold12).align_(\center).string_("class vars");
178                         gui.staticText.new(~window, Rect(0,0, 180, 24))
179                                 .font_(hvBold12).align_(\center).string_("instance vars");
180                         ~window.view.decorator.nextLine;
182                         ~classVarView = gui.listView.new(~window, Rect(0,0, 180, 130));
183                         ~instVarView = gui.listView.new(~window, Rect(0,0, 180, 130));
184                         ~classVarView.value = 0;
185                         ~instVarView.value = 0;
187                         ~window.view.decorator.nextLine;
189                         ~subclassTitle = gui.staticText.new(~window, Rect(0,0, 220, 24))
190                                 .font_(hvBold12).align_(\center).string_("subclasses  (press return)");
191                         ~methodTitle = gui.staticText.new(~window, Rect(0,0, 240, 24))
192                                 .font_(hvBold12).align_(\center).string_("methods");
193                         gui.staticText.new(~window, Rect(0,0, 200, 24))
194                                 .font_(hvBold12).align_(\center).string_("arguments");
195                         ~window.view.decorator.nextLine;
197                         ~subclassView = gui.listView.new(~window, Rect(0,0, 220, 260));
198                         ~methodView = gui.listView.new(~window, Rect(0,0, 240, 260));
199                         ~argView = gui.listView.new(~window, Rect(0,0, 200, 260));
200                         ~subclassView.resize = 4;
201                         ~methodView.resize = 4;
202                         ~argView.resize = 4;
204                         ~window.view.decorator.nextLine;
206                         [~classVarView, ~instVarView, ~subclassView, ~methodView, ~argView].do (
207                                 _.beginDragAction_({nil})
208                         );
209                 };
211                 this.addInstanceActions
212                         .makeState(class, \class);
214                 views.window.front;
215         }
217         makeState { |result, state = \class, addHistory = true, extraValuesDict|
218                 currentState = Environment(proto: views, parent: updateProtos, know: true).make {
219                         ~result = result;
220                         if(~result.isNil or: { result.isArray and: { result.isEmpty } }) {
221                                 ~state = ("empty" ++ state).asSymbol
222                         } {
223                                 ~state = state;
224                         };
225                         if(extraValuesDict.respondsTo(\keysValuesDo)) {
226                                 currentEnvironment.putAll(extraValuesDict);
227                         };
228                         ~state.envirGet[\init].value;
229                         ~subclassViewIndex = ~subclassViewIndex.value ? 0;
230                         ~methodViewIndex = ~methodViewIndex.value ? 0;
231                         ~argView.value = 0;
232                 };
233                 if (addHistory) {
234                         if (history.size > 1000) { history = history.drop(1) };
235                         historyPos = historyPos + 1;
236                         history = history.extend(historyPos).add(currentState);
237                 };
238                 this.updateViews;
239         }
241         restoreHistory {
242                         // note, don't use makeState because we already have the complete state saved
243                 currentState = history[historyPos];
244                 this.updateViews;
245         }
247         updateViews {
248                 var     updaters, bstate, view;
249                 currentState.use {
250                         updaters = ~state.envirGet;
251                         viewList.do { |viewname|
252                                 updaters[viewname].value(viewname.envirGet);
253                         };
254                         buttonSet.do { |viewname|
255                                 viewname.envirGet.tryPerform(\enabled_,
256                                         updaters[\buttonsDisabled].includes(viewname).not);
257                         };
258                         ~window.refresh;
259                 }
260         }
262         close {
263                 if(views.window.isClosed.not) { views.window.close };
264                 currentState = history = views = nil;           // dump garbage
265         }
267                 // workaround for the fact that you can't get a non-metaclass from a metaclass
268         *getClass { |method|
269                 var     class;
270                 if(method.isNil) { ^nil };
271                 ^if((class = method.ownerClass).isMetaClass) {
272                         class.name.asString[5..].asSymbol.asClass;
273                 } { class }
274         }
276         getClass { |method| ^this.class.getClass(method) }
278         searchClasses { |string, rootNumber, matchCase, addHistory = true|
279                 var     pool, rootdir, result,
280                         searchType = searchMenuKeys[rootNumber] ? \classSearch,
281                         isClassSearch;
282                 string = string ?? { "" };
283                 matchCase = matchCase > 0;
284                 switch(rootNumber)
285                         { 0 } {
286                                 isClassSearch = true;
287                                 pool = Class.allClasses
288                         }
289                         { 1 } {
290                                 isClassSearch = false;
291                                 pool = Class.allClasses;
292                         }
293                         { 2 } {
294                                 isClassSearch = true;
295                                 pool = this.currentClass.allSubclasses
296                         }
297                         { 3 } {
298                                 isClassSearch = false;
299                                 pool = this.currentClass.allSubclasses ++ this.currentClass;
300                                 if(this.currentClass.isMetaClass.not) {
301                                         pool = pool ++ pool.collect(_.class);
302                                 };
303                         }
304                         { 4 } {
305                                 isClassSearch = true;
306                                 rootdir = PathName(currentState.currentClass.filenameSymbol.asString).pathOnly;
307                                 pool = Class.allClasses.select({ |class|
308                                         PathName(class.filenameSymbol.asString).pathOnly == rootdir
309                                 });
311                                 if(string.isEmpty) {
312                                         this.makeState(
313                                                 pool.reject(_.isMetaClass).sort({ |a, b| a.name < b.name }),
314                                                 searchType
315                                         );
316                                         ^this   // just to force early exit
317                                 };
318                         };
319                 case { string.isEmpty } {
320                         result = pool.sort({ |a, b| a.name < b.name });
321                 }
322                 { isClassSearch } {
323                         result = (if(matchCase) {
324                                 pool.select({ |class|
325                                         class.isMetaClass.not and: { class.name.asString.contains(string) }
326                                 })
327                         } {
328                                 pool.select({ |class|
329                                         class.isMetaClass.not and: { class.name.asString.containsi(string) }
330                                 })
331                         }).sort({ |a, b| a.name < b.name });
332                 }
333                 {       // default case - method search
334                         result = Array.new;
335                         if(matchCase) {
336                                 pool.do({ |class|
337                                         result = result.addAll(class.methods.select({ |method|
338                                                 method.name.asString.contains(string)
339                                         }));
340                                 });
341                         } {
342                                 pool.do({ |class|
343                                         result = result.addAll(class.methods.select({ |method|
344                                                 method.name.asString.containsi(string)
345                                         }));
346                                 });
347                         };
348                         result = result.sort({ |a, b|
349                                 if(a.name == b.name) { a.ownerClass.name < b.ownerClass.name }
350                                         { a.name < b.name };
351                         });
352                 };
353                 this.makeState(result, searchType, addHistory, extraValuesDict: (
354                         searchString: string,
355                         searchMenuValue: rootNumber,
356                         caseButtonValue: matchCase.binaryValue
357                 ));
358         }
360         *initGUI {
361                 updateProtos ?? {
362                         gray = Color.grey(0.5);
364                         searchMenuKeys = #[classSearch, methodSearch, classSearch, methodSearch, classSearch];
365                         viewList = #[currentClassNameView, superClassNameView, filenameView,
366                                 classVarView, instVarView, subclassTitle, methodTitle, subclassView,
367                                 methodView, argView, searchMenu];
368                         buttonSet = IdentitySet[\superButton, \metaButton, \helpButton, \classSourceButton,
369                                 \methodSourceButton, \implementationButton, \refsButton, \svnButton];
370                                 // updateProtos holds instructions to update the GUI
371                                 // for each kind of browser result to display
372                         updateProtos = (
373                                 class: (
374                                         init: {
375                                                 ~setSubclassArray.value;
376                                                 ~setMethodArray.value;
377                                                 ~setCurrentClass.value(~result);
378                                                 ~setCurrentMethod.value(~methodArray[0]);
379                                         },
380                                                 // ~result must be a Class
381                                         currentClassNameView: { |v| v.string = ~currentClass.name.asString },
382                                         superClassNameView: { |v|
383                                                 v.string = "superclass: " ++ ~currentClass.superclass.tryPerform(\name).asString
384                                         },
385                                         filenameView: { |v| v.string = ~currentClass.filenameSymbol.asString },
386                                         classVarView: { |v|
387                                                 v.items = ~currentClass.classVarNames.asArray.collectAs
388                                                         ({|name| name.asString }, Array).sort
389                                         },
390                                         instVarView: { |v|
391                                                 v.items = ~currentClass.instVarNames.asArray.collectAs
392                                                         ({|name| name.asString }, Array).sort
393                                         },
394                                         subclassTitle: { |v|
395                                                 v.string_("subclasses (press return)")
396                                         },
397                                         subclassView: { |v|
398                                                 ~subclassView.items_(~subclassArray.collect({|class| class.name.asString }))
399                                                         .value_(~subclassViewIndex ? 0)
400                                                         .action_(~subclassViewNormalAction)
401                                                         .enterKeyAction_(~navigateToSubclassAction)
402                                                         .mouseDownAction_(~listViewDoubleClickAction);
403                                         },
404                                         methodTitle: { |v| v.string_("methods") },
405                                         methodView: { |v|
406                                                 var colorFunc = { |class, name|
407                                                         if(class.findOverriddenMethod(name.asSymbol).isNil) {
408                                                                 nil
409                                                         } {
410                                                                 Color.grey(0.5, 0.8)
411                                                         }
412                                                 };
413                                                 var classMethodColors = ~classMethodNames.collect { |name|
414                                                         colorFunc.value(~currentClass.class, name)
415                                                 };
416                                                 var methodColors = ~methodNames.collect { |name|
417                                                         colorFunc.value(~currentClass, name)
418                                                 };
419                                                 var methodNames = ~classMethodNames ++ ~methodNames;
420                                                 var colors = classMethodColors ++ methodColors;
422                                                 ~methodView.items_(methodNames)
423                                                         .value_(~methodViewIndex ? 0)
424                                                         .action_(~displayCurrentMethodArgsAction)
425                                                         .mouseDownAction_(~listViewDoubleClickAction)
426                                                         .tryPerform(\colors_, colors)
427                                         },
428                                         argView: { |v|
429                                                 if (~currentMethod.isNil or: { ~currentMethod.argNames.isNil }) {
430                                                         ~argView.items = ["this"];
431                                                 } {
432                                                         ~argView.items = ~currentMethod.argNames.collectAs( {|name, i|
433                                                                 var defval;
434                                                                 defval = ~currentMethod.prototypeFrame[i];
435                                                                 if (defval.isNil) { name.asString }{
436                                                                         name.asString ++ "     = " ++ defval.asString
437                                                                 }
438                                                         }, Array);
439                                                 };
440                                         },
441                                         searchMenu: { |v|
442                                                 v.items = ["All classes",
443                                                         "All methods",
444                                                         "Subclasses of " ++ ~currentClass.name,
445                                                         "% + subclass methods".format(~currentClass.name),
446                                                         "Classes in folder "
447                                                                 ++ PathName(~currentClass.filenameSymbol.asString)
448                                                                 .allFolders.last
449                                                 ];
450                                         },
451                                         buttonsDisabled: IdentitySet.new        // all buttons OK
452                                 ),
453                                         // this could happen if the user types a wrong class name into the class name box
454                                 emptyclass: (
455                                         currentClassNameView: { |v| v.string = "Class does not exist" },
456                                         superClassNameView: { |v| v.string = "" },
457                                         filenameView: { |v| v.string = "" },
458                                         classVarView: { |v| v.items_(#[]) },
459                                         instVarView: { |v| v.items_(#[]) },
460                                         subclassTitle: { |v| v.string = "no subclasses" },
461                                         methodTitle: { |v| v.string = "no methods" },
462                                         subclassView: { |v| v.items_(#[]) },
463                                         methodView: { |v| v.items_(#[]) },
464                                         argView: { |v| v.items_(#[]) },
465                                         buttonsDisabled: buttonSet      // all buttons disabled
466                                 ),
467                                 classSearch: (
468                                                 // init: set current class and current method
469                                         init: {
470                                                 ~currentClass = ~result[0];
471                                                 ~subclassArray = ~result;
472                                                 ~setMethodArray.value(~currentClass);
473                                                 ~currentMethod = ~methodArray[0];
474                                         },
475                                                 // result must be an array of classes
476                                         currentClassNameView: { |v| v.string = "Class Search: " ++ ~searchString },
477                                         superClassNameView: { |v| v.string = "" },
478                                         filenameView: { |v| ~class[\filenameView].value(v) },
479                                         classVarView: { |v| ~class[\classVarView].value(v) },
480                                         instVarView: { |v| ~class[\instVarView].value(v) },
481                                         subclassTitle: { |v| v.string = "matching classes (press return)" },
482                                         methodTitle: { |v| v.string = "methods" },
483                                         subclassView: { |v|
484                                                 v.items_(~result.collect(_.name))
485                                                         .value_(~subclassViewIndex ? 0)
486                                                         .action_(~classSearchSubclassViewAction)
487                                                         .enterKeyAction_(~navigateToCurrentClassAction);
488                                         },
489                                         methodView: { |v| ~class[\methodView].value(v) },
490                                         argView: { |v| ~class[\argView].value(v) },
491                                         buttonsDisabled: #[]    // all buttons OK
492                                 ),
493                                 emptyclassSearch: (
494                                         currentClassNameView: { |v| v.string = "Class Search: " ++ ~searchString },
495                                         superClassNameView: { |v| v.string = "No results found!" },
496                                         filenameView: { |v| v.string = "" },
497                                         classVarView: { |v| v.items_(#[]) },
498                                         instVarView: { |v| v.items_(#[]) },
499                                         subclassTitle: { |v| v.string = "no classes" },
500                                         methodTitle: { |v| v.string = "no methods" },
501                                         subclassView: { |v| v.items_(#[]) },
502                                         methodView: { |v| v.items_(#[]) },
503                                         argView: { |v| v.items_(#[]) },
504                                         buttonsDisabled: buttonSet      // all buttons disabled
505                                 ),
506                                 methodSearch: (
507                                         init: {
508                                                 ~methodArray = ~result;
509                                                 ~currentMethod = ~methodArray[0];
510                                                 ~methodViewIndex = 0;
511                                                 ~setClassArrayFromMethodSearch.value(~result);
512                                                 ~currentClass = this.getClass(~currentMethod);
513                                                 ~subclassViewIndex = ~subclassArray.indexOf(~currentClass);
514                                         },
515                                                 // result must be an array of methods
516                                         currentClassNameView: { |v| v.string = "Method Search: " ++ ~searchString },
517                                         superClassNameView: { |v| v.string = "" },
518                                         filenameView: { |v| v.string = ~currentMethod.filenameSymbol },
519                                         classVarView: { |v| ~class[\classVarView].value(v) },
520                                         instVarView: { |v| ~class[\instVarView].value(v) },
521                                         subclassTitle: { |v| v.string = "classes (press return)" },
522                                         methodTitle: { |v| v.string = "matching methods (press return)" },
523                                         subclassView: { |v|
524                                                 v.items_(~subclassArray.collect({ |class| class.name }))
525                                                         .value_(~subclassViewIndex ? 0)
526                                                         .action_(nil)
527                                                         .enterKeyAction_(~navigateToSubclassAction);
528                                         },
529                                         methodView: { |v|
530                                                 v.items_(~methodArray.collect({ |method|
531                                                         method.name ++ " (" ++ method.ownerClass ++ ")"
532                                                 })).action_(~methodSearchMethodViewAction)
533                                                 .value_(~methodViewIndex ? 0)
534                                                 .enterKeyAction_(~navigateToCurrentClassAction)
535                                         },
536                                         argView: { |v| ~class[\argView].value(v) },
537                                         buttonsDisabled: #[]    // all buttons OK
538                                 ),
539                                 emptymethodSearch: (
540                                         currentClassNameView: { |v| v.string = "Method Search: " ++ ~searchString },
541                                         superClassNameView: { |v| v.string = "No results found!" },
542                                         filenameView: { |v| v.string = "" },
543                                         classVarView: { |v| v.items_(#[]) },
544                                         instVarView: { |v| v.items_(#[]) },
545                                         subclassTitle: { |v| v.string = "no classes" },
546                                         methodTitle: { |v| v.string = "no methods" },
547                                         subclassView: { |v| v.items_(#[]) },
548                                         methodView: { |v| v.items_(#[]) },
549                                         argView: { |v| v.items_(#[]) },
550                                         buttonsDisabled: buttonSet      // all buttons disabled
551                                 ),
553                                         // support funcs shared in common
554                                 setCurrentClass: { |class|
555                                         ~currentClass = class ?? { ~subclassArray[~subclassView.value ? 0] };
556                                 },
557                                 setCurrentMethod: { |method|
558                                         ~currentMethod = method ?? { ~methodArray[~methodView.value ? 0] };
559                                 },
560                                 setSubclassArray: { |class|
561                                         class = class ? ~result;
562                                         if (class.subclasses.isNil) { ~subclassArray = []; } {
563                                                 ~subclassArray = class.subclasses.copy.sort {|a,b| a.name <= b.name };
564                                         };
565                                 },
566                                 setMethodArray: { |class|
567                                         class = class ? ~result;
568                                         if (class.class.methods.isNil) {
569                                                 ~classMethodArray = [];
570                                                 ~classMethodNames = [];
571                                         } {
572                                                 ~classMethodArray = class.class.methods.asArray.copy.sort
573                                                                                         {|a,b| a.name <= b.name };
574                                                 ~classMethodNames = ~classMethodArray.collect {|method|
575                                                         "*" ++ method.name.asString
576                                                 };
577                                         };
578                                         if (class.methods.isNil) {
579                                                 ~methodArray = [];
580                                                 ~methodNames = [];
581                                         } {
582                                                 ~methodArray = class.methods.asArray.copy.sort
583                                                                                         {|a,b| a.name <= b.name };
584                                                 ~methodNames = ~methodArray.collect {|method|
585                                                         method.name.asString
586                                                 };
587                                         };
588                                         ~methodArray = ~classMethodArray ++ ~methodArray;
589                                 },
590                                 setClassArrayFromMethodSearch: { |result|
591                                         var     classSet = IdentitySet.new, class;
592                                         ~result.do({ |method|
593                                                 classSet.add(this.getClass(method));
594                                         });
595                                         ~subclassArray = classSet.asArray.sort({ |a, b| a.name < b.name });
596                                 }
597                         );
598                 }
599         }
601                 // because these use instance variables, they cannot be defined in the above class method
602                 // each ClassBrowser instance gets a separate dictionary with the proper object scope
603         addInstanceActions {
604                 views.putAll((
605                                 // these are called from GUI, cannot assume currentState is already in force
606                         navigateToSubclassAction: {
607                                 var     prevState = currentState, prevMethod = prevState.currentMethod;
608                                 this.makeState(currentState.subclassArray[currentState.subclassView.value], \class,
609                                                 // methodViewIndex func runs in context
610                                                 // of the new state environment
611                                                 // might be nil unless subclass also implements
612                                         extraValuesDict: (methodViewIndex: {
613                                                 if(prevMethod.isNil) { 0 } {
614                                                         ~methodArray.detectIndex({ |item|
615                                                                 item.name == prevMethod.name
616                                                         })
617                                                 };
618                                         }));
619                         },
620                         navigateToCurrentClassAction: {
621                                 var     prevState = currentState;
622                                 this.makeState(currentState.currentClass, \class,
623                                         extraValuesDict: (methodViewIndex: {
624                                                 ~methodArray.indexOf(prevState.currentMethod)
625                                         }));
626                         },
627                         subclassViewNormalAction: { |view|
628                                 currentState.subclassViewIndex = view.value;
629                         },
630                         displayCurrentMethodArgsAction: { |view|
631                                 currentState.use {
632                                         ~methodViewIndex = view.value;
633                                         ~setCurrentMethod.value;
634                                         ~class[\argView].value(~argView)
635                                 }
636                         },
637                         methodEnterKeyAction: {
638                                 currentState.currentMethod !? { currentState.currentMethod.openCodeFile };
639                         },
640                         classSearchSubclassViewAction: { |view|
641                                 currentState.use {
642                                         ~subclassViewIndex = view.value;
643                                         ~setCurrentClass.value;
644                                         ~setMethodArray.value(~currentClass);
645                                         ~methodViewIndex = 0;
646                                         ~class[\filenameView].value(~filenameView);
647                                         ~class[\methodView].value(~methodView);
648                                         ~class[\argView].value(~argView);
649                                         ~class[\classVarView].value(~classVarView);
650                                         ~class[\instVarView].value(~instVarView);
651                                 }
652                         },
653                         methodSearchMethodViewAction: { |view|
654                                 currentState.use {
655                                         var     index;
656                                         ~methodViewIndex = view.value;
657                                         ~currentMethod = ~methodArray[~methodView.value ? 0];
658                                         ~currentClass = this.getClass(~currentMethod);
659                                         ~methodSearch[\filenameView].value(~filenameView);
660                                         ~class[\argView].value(~argView);
661                                         ~class[\classVarView].value(~classVarView);
662                                         ~class[\instVarView].value(~instVarView);
663                                         if((index = ~subclassArray.indexOf(~currentClass)).notNil) {
664                                                 ~subclassView.value = index;
665                                                 ~subclassViewIndex = index;
666                                         };
667                                 };
668                         },
669                         listViewDoubleClickAction: { |view, x, y, mods, buttonNumber, clickCount|
670                                 if(clickCount == 2) { view.enterKeyAction.value }
671                         }
672                 ));
673         }