Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / SCClassLibrary / Common / Quarks / Quarks.sc
blob8a097e537ca4ecfe5b2b3a48a49ecf3e2b5dc9aa
1 /**
2   *
3   * Subversion based package repository and package manager
4   * sk, cx, danstowell, LFSaw
5   *
6   *  this is the interface class for
7   *   accessing the SVN repository, (eg. the sourceforge quarks project)
8   *   downloading those to the local quarks folder (Platform.userAppSupportDir/quarks)
9   *   installing individual quarks by symlinking from the local quark folder into the [Platform dependent]/Extensions folder
10   *
11   */
14 Quarks
16         classvar global, <allInstances, <known;
17         var <repos, <local;
19         *global { ^(global ?? { global = this.new; }) }
21         *new { | reposPath, localPath |
22                 var newQ;
23                 if((this.known[reposPath].isNil.not) and: (this.known[reposPath] != localPath), {
24                         ("The repository is in the list of known repositories, but with a different local path. You are recommended to use this local path:"
25                                          + this.known[reposPath]).warn;
26                 });
27                 newQ = super.new.initQuarks(
28                         reposPath,
29                         localPath
30                 );
31                 allInstances.add(newQ);
32                 ^newQ;
33         }
34         initQuarks{|reposPath, localPath|
35                 local = LocalQuarks(localPath, this);
36                 repos = QuarkSVNRepository(reposPath, local);
37         }
39         *initClass {
40                 Class.initClassTree( Platform );
41                 allInstances = List(1);
42                 known = Dictionary[
43                         "https://quarks.svn.sourceforge.net/svnroot/quarks" -> (Platform.userAppSupportDir +/+ "quarks"),
44                         "https://svn.sonenvir.at/svnroot/SonEnvir/trunk/src/quarks-sonenvir" -> (Platform.userAppSupportDir +/+ "quarks-sonenvir"),
45                         "https://sc3-plugins.svn.sourceforge.net/svnroot/sc3-plugins/build" -> (Platform.userAppSupportDir +/+ "SC3plugins")
46                         ];
47         }
48         *forUrl { |url|
49                 var q;
50                 this.global; // ensure the global one is constructed
51                 q = allInstances.detect({|q| q.repos.url == url});
52                 if(q.isNil && this.known[url].isNil.not, {
53                         q = Quarks.new(url, this.known[url]);
54                 });
55                 ^q;
56         }
58         update { |name|
59                 var q;
60                 if(name.notNil,{
61                         if((q = local.findQuark(name)).isNil,{
62                                 Error("Local Quark "+name+" not found, cannot update from repository").throw;
63                         });
64                         repos.svn("update", (local.path+/+q.path).escapeChar($ ));
65                 },{
66                         //update all
67                         repos.update(local)
68                 });
69         }
70         /*add { |name|
71                 // this should create or manage the directory file too if it exists
72                 var q;
73                 if((q = local.findQuark(name)).isNil,{
74                         Error("Local Quark code not found, cannot add to repository").throw;
75                 });
76                 repos.svn("add",local.path++"/"++q.path);
77         }*/
78         commit { |name,message|
79                 var q;
80                 if((q = local.findQuark(name)).isNil,{
81                         Error("Local Quark code not found, cannot commit to repository").throw;
82                 });
83                 if(message.isNil,{
84                         Error("svn log message required to commit").throw;
85                 });
86                 repos.svn("commit","-m",message,"-F",local.path +/+ q.path);
87         }
88         // TODO: Deprecate "checkoutDirectory" - "updateDirectory" can be used whether or not there's an existing one
89         checkoutDirectory {
90                 "Please wait for directory to be checked out.".postln;
91                 ^this.updateDirectory
92         }
93         updateDirectory {
94                 repos.updateDirectory
95         }
97         listCheckedOut {
98                 this.checkedOut.do { |q| q.postDesc };
99         }
100         listAvailable {
101                 this.repos.quarks.do { |q| q.postDesc };
102         }
104         checkoutAll { repos.checkoutAll(local.path) }
105         checkout { |name, version, sync=false|
106                 var q;
107                 if(local.findQuark(name,version).notNil,{
108                         inform("Quark "++name++" already checked out");
109                         ^this
110                 });
112                 q = repos.findQuark(name,version);
113                 if(q.isNil,{
114                         Error("Quark not found in repository.").throw;
115                 });
116                 repos.checkout(q, local.path, sync);
117         }
118         // DEPRECATED because it has a different and confusing functionality w.r.t. QuarkSVNRepos.checkDir
119         checkDir {
120                 var d;
121                 "Quarks.checkDir is deprecated".warn;
122                 d = (Platform.userExtensionDir +/+ local.name).escapeChar($ );
123                 if(d.pathMatch.isEmpty,{
124                         ("creating: " + d).inform;
125                         d.mkdir;
126                 });
127         }
128         checkedOut {
129                 ^local.quarks
130         }
131         status { |name|
132                 var q;
133                 if(name.notNil,{
134                         q = local.findQuark(name);
135                         repos.svn("status",(local.path +/+ q.path).escapeChar($ ));
136                 },{
137                         repos.svn("status",local.path.escapeChar($ ));
138                 });
139         }
140         installed {
141                 // of quarks in local, select those also present in userExtensionDir
142                 ^local.quarks.select{|q|
143                         (Platform.userExtensionDir.escapeChar($ )
144                                 +/+ local.name
145                                 +/+ q.path
146                         ).pathMatch.notEmpty
147                 }
148         }
149         install { | name , includeDependencies=true, checkoutIfNeeded=true |
150                 var q, deps, installed, dirname, quarksForDep;
152                 if(this.isInstalled(name),{
153                         (name + "already installed").inform;
154                         ^this
155                 });
157                 q = local.findQuark(name);
158                 if(q.isNil,{
159                         if(checkoutIfNeeded) {
160                                 (name.asString + " not found in local quarks; checking out from remote ...").postln;
161                                 this.checkout(name, sync: true);
162                                 q = local.reread.findQuark(name);
163                                 if(q.isNil, {
164                                         Error("Quark" + name + "install: checkout failed.").throw;
165                                 });
166                         }
167                         {
168                                 Error(name.asString + "not found in local quarks.  Not yet downloaded from the repository ?").throw;
169                         };
170                 });
172                 if(q.isCompatible.not,{
173                         (q.name + " reports that it is not compatible with your current class library.  See the help file for further information.").inform;
174                         ^this
175                 });
177                 // create /quarks/ directory if needed
178                 if(this.repos.checkDir.not){this.checkoutDirectory};
180                 // Now ensure that the dependencies are installed (if available given the current active reposses)
181                 if(includeDependencies, {
182                         q.dependencies(true).do({ |dep|
183                                 quarksForDep = if(dep.repos.isNil, {this}, {Quarks.forUrl(dep.repos)});
184                                 if(quarksForDep.isNil, {
185                                         ("Quarks:install - unable to find repository for dependency '" ++ dep.name
186                                                 ++ "' - you may need to satisfy this dependency manually. No repository detected locally with URL "++dep.repos).warn;
187                                 }, {
188                                         if(quarksForDep.isInstalled(dep.name).not, {
189                                                 try({
190                                                         quarksForDep.install(dep.name, false, checkoutIfNeeded)
191                                                 }, {
192                                                         ("Unable to satisfy dependency of '"++name++"' on '"++dep.name
193                                                                 ++"' - you may need to install '"++dep.name++"' manually.").warn;
194                                                 });
195                                         });
196                                 });
197                         });
198                 });
200                 // Ensure the correct folder-hierarchy exists first
201                 dirname = (Platform.userExtensionDir +/+  local.name +/+ q.path).dirname;
202                 if(File.exists(dirname).not, {
203                         dirname.mkdir
204                 });
206                 // install via symlink to Extensions/<quarks-dir>
207                 ("ln -s " +  (local.path +/+ q.path).escapeChar($ ) +  (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
208                 (q.name + "installed").inform;
209         }
210         listInstalled {
211                 this.installed.do { |q| q.postDesc };
212         }
213         isInstalled { arg name;
214                 ^this.installed.detect{|quark| quark.name == name }.notNil
215         }
216         uninstall { | name |
217                 var q, deps, installed;
218                 name = name.asString;
219                 if(this.isInstalled(name).not,{
220                         ^this
221                 });
223                 q = local.findQuark(name);
224                 if(q.isNil,{
225                         Error(
226                                 name +
227                                 "is not found in Local quarks in order to look up its relative path.  You may remove the symlink manually."
228                         ).throw;
229                 });
231                 // install via symlink to Extensions/Quarks
232                 ("rm " +  (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
233                 (q.name + "uninstalled").inform;
234         }
236         help { |name|
237                 var q;
238                 if(name.isNil){
239                         "Quarks".help
240                 }{
241                         q = local.findQuark(name);
242                         if(q.isNil,{
243                                 Error(
244                                         name.asString +
245                                         "not found in local quarks.  Not yet downloaded from the repository ?"
246                                 ).throw;
247                         });
248                         q.help;
249                 }
250         }
252         name { ^local.name }
254         ///// convenience methods for Quarks.global
256         *checkoutAll { this.global.repos.checkoutAll(this.global.local.path) }
257         *checkout { | name, version, sync | this.global.checkout(name,version, sync); }
259         /*
260          return Quark objects for each in App Support/SuperCollider/Quarks
261          (so actually this would list any Quarks that are in local development
262          and not yet checked in) */
263         *checkedOut { ^this.global.checkedOut }
264         *listCheckedOut {
265                 this.checkedOut.do { |q| q.postDesc };
266         }
267         *listAvailable {
268                 this.global.listAvailable
269         }
270         /*
271           download/update only the quark specification files from remote repos
272           and not the quarks themselves */
273         *updateDirectory {
274                 ^this.global.repos.updateDirectory
275         }
276         *checkoutDirectory {
277                 ^this.global.checkoutDirectory
278         }
279         *update { |quarkName| this.global.update(quarkName) }
281         /*
282           this symlinks from {App Support}/SuperCollider/Quarks to
283           {App Support}/SuperCollider/Extensions
284           it is then in the SC compile path */
285         *install { |name, includeDependencies=true, checkoutIfNeeded=true|
286                 this.global.install(name, includeDependencies, checkoutIfNeeded)
287         }
289         /*
290           return Quark objects for each installed */
291         *installed { ^this.global.installed }
292         *listInstalled { ^this.global.listInstalled }
293         *isInstalled { |name| ^this.global.isInstalled(name) }
294         /*
295           removes the symlink */
296         *uninstall { |name| ^this.global.uninstall(name) }
298         /*
299           add code in {App Support}/SuperCollider/Quarks to the remote repos
300           and also adds the quarks file in DIRECTORY */
301         //*add { |name| this.global.add(name) }
302         /*
303           you may also use standard svn tools within {App Support}/SuperCollider/Quarks */
304         *commit { |name,message| this.global.commit(name,message) }
305         // post the SVN status
306         *status { |name| this.global.status(name) }
308         *local { ^this.global.local }
309         *repos { ^this.global.repos }
310         *help  {|name| ^this.global.help(name) }
311         *gui { ^this.global.gui }
313         gui {
314                 if( GUI.id === \qt ) { ^QuarksViewQt(this) } { ^QuarksView(this) }
315         }
318 // a gui for Quarks. 2007 by LFSaw.de
319 QuarksView {
320         var quarksCtrl, quarks;
322         var window, caption, explanation, views, resetButton, saveButton, warning,
323                 scrollview, scrB, flowLayout, /* quarksflow, */ height, maxPerPage, nextButton, prevButton;
325         *new { |quarksCtrl| ^super.new.init(quarksCtrl) }
327         init { |q|
328                 var pageStart = 0, fillPage;
330                 quarksCtrl = q;
332                 fillPage = { |start|
333                         scrollview.visible = false;
334                         views.notNil.if({
335                                 views.do({ |view| view.remove });
336                         });
337                         scrollview.decorator.reset;
338                         views = quarks.sort { |a, b| a.name < b.name }
339                                 .collect { |quark|
340                                         var qView = QuarkView.new(scrollview, 500@20, quark,
341                                                 quarksCtrl.installed.detect { |it| it == quark }.notNil
342                                         );
343                                         scrollview.decorator.nextLine;
344                                         qView;
345                                 };
346                         scrollview.visible = true;
347                         views
348                 };
350                 // note, this doesn't actually contact svn
351                 // it only reads the DIRECTORY entries you've already checked out
352                 quarks = quarksCtrl.repos.quarks.copy;
354                 scrB = GUI.window.screenBounds;
355                 height = min(quarks.size * 25 + 120, scrB.height - 60);
357                 window = GUI.window.new(quarksCtrl.name, Rect.aboutPoint( scrB.center, 250, height.div( 2 )));
358                 flowLayout = FlowLayout( window.view.bounds );
359                 window.view.decorator = flowLayout;
361                 caption = GUI.staticText.new(window, Rect(20,15,400,30));
362                 caption.font_( Font.sansSerif( 24 ));
363                 caption.string = quarksCtrl.name;
364                 window.view.decorator.nextLine;
366                 if ( quarks.size == 0 ){
367                         GUI.button.new(window, Rect(0, 0, 229, 20))
368                         .states_([["checkout Quarks DIRECTORY"]])
369                         .action_({ quarksCtrl.checkoutDirectory; });
370                 }{
371                         GUI.button.new(window, Rect(0, 0, 229, 20))
372                         .states_([["update Quarks DIRECTORY"]])
373                         .action_({ quarksCtrl.updateDirectory;});
374                 };
376                 GUI.button.new(window, Rect(0, 0, 200, 20))
377                 .states_([["refresh Quarks listing"]])
378                 .action_({
379                         window.close;
380                         quarksCtrl.gui;
381                 });
383                 window.view.decorator.nextLine;
385                 GUI.button.new(window, Rect(0, 0, 150, 20))
386                         .states_([["browse all help"]])
387                         .action_({ HelpBrowser.openBrowsePage("Quarks") });
389                 GUI.button.new(window, Rect(15,15,150,20))
390                 .states_([["open quark directory"]])
391                 .action_{ arg butt;
392                         openOS(quarksCtrl.local.path.escapeChar($ ))
393                 };
395                 resetButton = GUI.button.new(window, Rect(15,15,75,20));
396                 resetButton.states = [
397                         ["reset"]
398                 ];
399                 resetButton.action = { arg butt;
400                         views.do(_.reset);
401                 };
403                 saveButton = GUI.button.new(window, Rect(15,15,75,20));
404                 saveButton.states = [
405                         ["save", nil, Color.blue(1, 0.2)]
406                 ];
407                 saveButton.action = { arg butt;
408                         Task{
409                                 warning.string = "Applying changes, please wait";
410                                 warning.background_(Color(1.0, 1.0, 0.9));
411                                 0.1.wait;
412                                 views.do{|qView|
413                                         qView.toBeInstalled.if({
414                                                 quarksCtrl.install(qView.quark.name);
415                                                 qView.flush
416                                         });
417                                         qView.toBeDeinstalled.if({
418                                                 quarksCtrl.uninstall(qView.quark.name);
419                                                 qView.flush;
420                                         })
421                                 };
422                                 warning.string = "Done. You should now recompile sclang";
423                                 warning.background_(Color(0.9, 1.0, 0.9));
424                         }.play(AppClock);
425                 };
427                 window.view.decorator.nextLine;
428                 explanation = GUI.staticText.new(window, Rect(20,15,500,20));
429                 explanation.string = "\"+\" -> installed, \"-\" -> not installed, \"*\" -> marked to install, \"x\" -> marked to uninstall";
430                 window.view.decorator.nextLine;
432                 warning = GUI.staticText.new(window, Rect(20,15,400,30));
433                 warning.font_( Font.sansSerif( 18 ));
435                 window.view.decorator.nextLine;
436                 GUI.staticText.new( window, 492 @ 1 ).background_( Color.grey );
437                 window.view.decorator.nextLine;
439                 flowLayout.margin_( 0 @0 ).gap_( 0@0 );
440                 scrollview = GUI.scrollView.new(window, 500 @ (height - 165))
441                         .resize_( 5 )
442                         .autohidesScrollers_(true);
443                 scrollview.decorator = FlowLayout( Rect( 0, 0, 500, quarks.size * 25 + 20 ));
445                 window.front;
446                 fillPage.(pageStart);
447                 ^window;
448         }
451 QuarksViewQt {
452         var quarksCtrl;
454         var window, lblCaption,
455                 btnUpdate, btnHelp, btnUpdateQuarks, btnOpenDir, btnReset, btnApply,
456                 lblStatus, lblExplanation, quarksView,
457                 infoView, btnQuarkHelp, btnQuarkOpen, btnQuarkClasses,
458                 btnQuarkMethods, txtDescription, btnCloseDetails;
459         var quarks, views, curQuark;
460         var refresh, msgWorking, msgDone;
461         var screen, palette, gizmo;
463         *new { |quarksCtrl| ^super.new.init(quarksCtrl) }
465         init { |q|
466                 quarksCtrl = q;
468                 refresh = {
469                         quarksView.invokeMethod( \clear );
470                         quarks = quarksCtrl.repos.quarks.copy;
471                         quarksView.canSort = false;
472                         views = quarks.collect{|quark|
473                                 var qView = QuarkViewQt.new(quarksView, quark,
474                                         quarksCtrl.installed.detect{|it| it == quark}.notNil);
475                                 qView;
476                         };
477                         quarksView.canSort = true;
478                         quarksView.sort( 1 );
479                         quarksView.invokeMethod( \resizeColumnToContents, 0 );
480                         quarksView.invokeMethod( \resizeColumnToContents, 1 );
481                         views
482                 };
484                 msgWorking = { arg msg;
485                         lblStatus.background = palette.button.blend(Color.yellow,0.2);
486                         lblStatus.string = msg;
487                 };
489                 msgDone =  { arg msg;
490                         lblStatus.background = palette.button.blend(Color.green,0.2);
491                         lblStatus.string = msg;
492                 };
494                 palette = GUI.current.palette;
495                 screen = Window.flipY(Window.availableBounds);
496                 window = Window( quarksCtrl.name, Rect( 0, 0, 700, screen.height * 0.9 ).center_(screen.center) );
498                 lblCaption = StaticText().font_( GUI.font.new(size:16,usePointSize:true) ).string_(quarksCtrl.name);
500                 btnUpdate = Button()
501                         .states_([["Update Quark Listing"]])
502                         .toolTip_("Download the latest information and update the Quarks listing")
503                         .action_({
504                                 quarksView.enabled = false;
505                                 msgWorking.value("Downloading the latest information...");
506                                 AppClock.sched( 0.2, {
507                                         protect {
508                                                 quarksCtrl.updateDirectory;
509                                         } {
510                                                 refresh.value;
511                                                 quarksView.enabled = true;
512                                                 msgDone.value("Quarks listing has been updated with the latest information.")
513                                         }
514                                 });
515                                 nil
516                         });
518                 btnUpdateQuarks = Button()
519                         .states_([["Update Quarks"]])
520                         .toolTip_("Update installed Quarks")
521                         .action_({
522                                 quarksView.enabled = false;
523                                 msgWorking.value("Updating installed Quarks...");
524                                 AppClock.sched( 0.2, {
525                                         protect {
526                                                 quarksCtrl.update;
527                                         } {
528                                                 refresh.value;
529                                                 quarksView.enabled = true;
530                                                 msgDone.value("Quarks have been updated." +
531                                                         " You should recompile the class library for changes to take effect.")
532                                         }
533                                 });
534                                 nil
535                         });
537                 btnHelp = Button().states_([["Help"]])
538                         .toolTip_("Browse help for all the Quarks")
539                         .action_({ HelpBrowser.openBrowsePage("Quarks") });
541                 btnOpenDir = Button().states_([["Directory"]])
542                         .toolTip_("Open the local Quarks directory")
543                         .action_({ openOS(quarksCtrl.local.path) });
545                 btnReset = Button()
546                         .states_([["Reset"]])
547                         .toolTip_("Clear the marked changes")
548                         .action_({ arg butt; views.do(_.reset) });
550                 btnApply = Button().states_([["Apply",nil,Color.blue.blend(palette.button,0.6)]])
551                         .toolTip_("Apply the marked changes")
552                         .action_({ arg butt;
553                                 quarksView.enabled = false;
554                                 msgWorking.value("Applying changes, please wait...");
555                                 AppClock.sched( 0.2, {
556                                         protect {
557                                                 views.do{|qView|
558                                                         qView.toBeInstalled.if({
559                                                                 quarksCtrl.install(qView.quark.name);
560                                                                 qView.flush
561                                                         });
562                                                         qView.toBeDeinstalled.if({
563                                                                 quarksCtrl.uninstall(qView.quark.name);
564                                                                 qView.flush;
565                                                         })
566                                                 };
567                                         } {
568                                                 msgDone.value("Changes applied." +
569                                                         "You should recompile the class library for changes to take effect."
570                                                 );
571                                                 quarksView.enabled = true;
572                                         }
573                                 });
574                         });
576                 lblExplanation = StaticText().string_(
577                         "\"+\" installed, \"-\" not installed, \"*\" to install, \"x\" to uninstall"
578                 );
580                 lblStatus = StaticText().font_( GUI.font.new( size:12, usePointSize:true ) );
582                 quarksView = TreeView()
583                         .setProperty( \rootIsDecorated, false )
584                         .columns_([nil,"Name","Summary"])
585                         .itemPressedAction_({ |v|
586                                 infoView.visible = true;
587                         })
588                         .onItemChanged_({ |v|
589                                 var curItem, curView, inst;
590                                 curItem = v.currentItem;
591                                 curQuark = nil;
592                                 if( curItem.notNil ) {
593                                         curView = views.detect { |view| view.treeItem == curItem };
594                                         if( curView.notNil ) {
595                                                 curQuark = curView.quark;
596                                                 txtDescription.string = curQuark.longDesc;
597                                                 btnQuarkOpen.enabled = curQuark.isLocal;
598                                                 inst = quarksCtrl.installed.detect{|it| it == curQuark}.notNil;
599                                                 btnQuarkClasses.enabled = inst;
600                                                 btnQuarkMethods.enabled = inst;
601                                         }
602                                 }{
603                                         infoView.visible = false
604                                 }
605                         });
607                 txtDescription = TextView(bounds:10@10)
608                         .font_( GUI.font.new( size:10, usePointSize:true ) )
609                         .tabWidth_(15)
610                         .autohidesScrollers_( true )
611                         .hasVerticalScroller_( true )
612                         .editable_( false )
613                         //.minSize_(Size(0,0));
614                         .minHeight_(50);
616                 btnQuarkHelp = Button()
617                         .states_([["Help"]])
618                         .toolTip_("Show help for this Quark")
619                         .action_({
620                                 curQuark.help
621                         });
623                 btnQuarkOpen = Button()
624                         .states_([["Source"]])
625                         .toolTip_("Open the source directory of this Quark")
626                         .action_({
627                                 openOS( "%/%".format(Quarks.local.path, curQuark.path) );
628                         });
630                 btnQuarkClasses = Button()
631                         .states_([["Classes"]])
632                         .toolTip_("Show list of classes defined by this Quark")
633                         .enabled_(false)
634                         .action_({
635                                 var cls = curQuark.definesClasses;
636                                 var tree, item, buts = [
637                                         Button().states_([["Browse"]]).action_({
638                                                 cls[item.index].browse;
639                                         }),
640                                         Button().states_([["Help"]]).action_({
641                                                 cls[item.index].help;
642                                         }),
643                                         Button().states_([["Source"]]).action_({
644                                                 cls[item.index].openCodeFile;
645                                         })
646                                 ];
647                                 buts.do(_.enabled_(false));
648                                 Window("% Classes".format(curQuark.name)).layout_(
649                                         VLayout(
650                                                 tree = TreeView()
651                                                         .setProperty( \rootIsDecorated, false )
652                                                         .columns_(["Classes"])
653                                                         .onItemChanged_({|v| item = v.currentItem}),
654                                                 HLayout(*buts)
655                                         )
656                                 ).front;
657                                 if(cls.size>0) {
658                                         cls.do {|c| tree.addItem([c.name.asString])};
659                                         tree.itemPressedAction = { buts.do(_.enabled_(true)) }
660                                 } {
661                                         tree.addItem(["No classes"]);
662                                 };
663                                 tree.invokeMethod( \resizeColumnToContents, 0 );
664                         });
666                 btnQuarkMethods = Button()
667                         .states_([["Ext methods"]])
668                         .toolTip_("Show list of extension methods added by this Quark")
669                         .enabled_(false)
670                         .action_({
671                                 var mets = curQuark.definesExtensionMethods;
672                                 var tree, item, buts = [
673                                         Button().states_([["Browse"]]).action_({
674                                                 mets[item.index].ownerClass.browse;
675                                         }),
676                                         Button().states_([["Help"]]).action_({
677                                                 mets[item.index].help;
678                                         }),
679                                         Button().states_([["Source"]]).action_({
680                                                 mets[item.index].openCodeFile;
681                                         })
682                                 ];
683                                 buts.do(_.enabled_(false));
684                                 Window("% Extension Methods".format(curQuark.name)).layout_(
685                                         VLayout(
686                                                 tree = TreeView()
687                                                         .setProperty( \rootIsDecorated, false )
688                                                         .columns_(["Class","Method"])
689                                                         .onItemChanged_({|v| item = v.currentItem}),
690                                                 HLayout(*buts)
691                                         )
692                                 ).front;
693                                 if(mets.size>0) {
694                                         mets.collect{|m|
695                                                 var x = m.ownerClass.name;
696                                                 tree.addItem(if(x.isMetaClassName) {[x.asString[5..],"*"++m.name]} {[x.asString,"-"++m.name]});
697                                         };
698                                         tree.itemPressedAction = { buts.do(_.enabled_(true)) }
699                                 } {
700                                         tree.addItem([nil,"No extension methods"]);
701                                 };
702                                 tree.invokeMethod( \resizeColumnToContents, 0 );
703                                 tree.invokeMethod( \resizeColumnToContents, 1 );
704                         });
706                 btnCloseDetails = StaticText()
707                         .string_("X")
708                         .align_(\center)
709                         .toolTip_("Hide Quark information panel")
710                         .mouseDownAction_({
711                                 infoView.visible = false;
712                         });
713                 gizmo = btnCloseDetails.sizeHint;
714                 gizmo.width = gizmo.width + 20;
715                 btnCloseDetails.fixedSize = gizmo;
717                 infoView = View();
718                 infoView.layout = VLayout(
719                         HLayout( btnCloseDetails, btnQuarkHelp, btnQuarkOpen, btnQuarkClasses, btnQuarkMethods, nil ).margins_(0),
720                         txtDescription
721                 ).spacing_(0).margins_(0);
722                 infoView.visible = false;
724                 window.layout =
725                         VLayout(
726                                 lblCaption,
727                                 HLayout( btnUpdate, btnUpdateQuarks, btnOpenDir, btnHelp, nil ),
728                                 lblStatus,
729                                 HLayout( btnReset, btnApply, [lblExplanation, s:1] ).margins_(0),
730                                 [quarksView, s:5],
731                                 [infoView, s:2]
732                         );
734                 refresh.value;
735                 window.front;
736                 ^window;
737         }