deprecate SCViewHolder-layRight
[supercollider.git] / SCClassLibrary / Common / GUI / HelpBrowser.sc
blob68a2daba82b768f480bd56d622940fcd0cb59b2b
1 HelpBrowser {
2         classvar singleton;
3         classvar <>defaultHomeUrl;
4         classvar <>openNewWindows = false;
6         var <>homeUrl;
7         var <window;
8         var webView;
9         var animCount = 0;
10         var srchBox;
11         var openNewWin;
12         var rout;
14         *initClass {
15                 StartUp.add {
16                         NotificationCenter.register(SCDoc, \docMapDidUpdate, this) {
17                                 if(WebView.implClass.respondsTo(\clearCache)) {
18                                         WebView.clearCache;
19                                 }
20                         }
21                 }
22         }
24         *instance {
25                 if( singleton.isNil ) {
26                         singleton = this.new;
27                         singleton.window.onClose = {
28                                 singleton.stopAnim;
29                                 singleton = nil;
30                         };
31                 };
32                 ^singleton;
33         }
35         *new { arg aHomeUrl, newWin;
36                 if( aHomeUrl.isNil ) {
37                         aHomeUrl = defaultHomeUrl ?? { SCDoc.helpTargetDir ++ "/Help.html" };
38                 };
39                 ^super.new.init( aHomeUrl, newWin ?? { openNewWindows } );
40         }
42         *goTo {|url|
43                 if(openNewWindows,{this.new},{this.instance}).goTo(url);
44         }
46         *openBrowsePage {|category|
47                 category = if(category.notNil) {"#"++category} {""};
48                 this.goTo(SCDoc.helpTargetDir++"/Browse.html"++category);
49         }
50         *openSearchPage {|text|
51                 text = if(text.notNil) {"#"++text} {""};
52                 this.goTo(SCDoc.helpTargetDir++"/Search.html"++text);
53         }
54         *openHelpFor {|text|
55                 this.goTo(SCDoc.findHelpFile(text));
56         }
57         *openHelpForMethod {|method|
58                 var cls = method.ownerClass;
59                 var met = method.name.asString;
60                 if(cls.isMetaClass) {
61                         cls = cls.name.asString.drop(5);
62                         met = "*"++met;
63                 } {
64                         cls = cls.name.asString;
65                         met = "-"++met;
66                 };
67                 this.goTo(Help.dir+/+"Classes"+/+cls++".html#"++met);
68         }
69         *getOldWrapUrl {|url|
70                 var c;
71                 ^("file://" ++ SCDoc.helpTargetDir +/+ "OldHelpWrapper.html#"++url++"?"++
72                 SCDoc.helpTargetDir +/+ if((c=url.basename.split($.).first).asSymbol.asClass.notNil)
73                         {"Classes" +/+ c ++ ".html"}
74                         {"Guides/WritingHelp.html"})
75         }
76         cmdPeriod { rout.play(AppClock) }
77         goTo {|url, brokenAction|
78                 var newPath, oldPath, plainTextExts = #[".sc",".scd",".txt",".schelp"];
80                 //FIXME: since multiple scdoc queries can be running at the same time,
81                 //it would be best to create a queue and run them in order, but only use the url from the last.
83                 plainTextExts.do {|x|
84                         if(url.endsWith(x)) {
85                                 ^this.openTextFile(url);
86                         }
87                 };
89                 window.front;
90                 this.startAnim;
92                 brokenAction = brokenAction ? {SCDoc.helpTargetDir++"/BrokenLink.html#"++url};
94                 rout = Routine {
95                         try {
96                                 url = SCDoc.prepareHelpForURL(url) ?? brokenAction;
97                                 #newPath, oldPath = [url,webView.url].collect {|x|
98                                         if(x.notEmpty) {x.findRegexp("(^\\w+://)?([^#]+)(#.*)?")[1..].flop[1][1]}
99                                 };
100                                 // detect old helpfiles and open them in OldHelpWrapper
101                                 if(block{|break| Help.do {|key,path| if(url.endsWith(path)) {break.value(true)}}; false}) {
102                                         url = HelpBrowser.getOldWrapUrl(url)
103                                 };
104                                 webView.url = url;
105                                 // needed since onLoadFinished is not called if the path did not change:
106                                 if(newPath == oldPath) {webView.onLoadFinished.value};
107                                 webView.focus;
108                         } {|err|
109                                 webView.html = err.errorString;
110                                 err.throw;
111                         };
112                         CmdPeriod.remove(this);
113                         rout = nil;
114                 }.play(AppClock);
115                 CmdPeriod.add(this);
116         }
118         goHome { this.goTo(homeUrl); }
120         goBack { webView.back; }
122         goForward { webView.forward; }
124 /* ------------------------------ private ------------------------------ */
126         init { arg aHomeUrl, aNewWin;
127                 var toolbar;
128                 var lblFind, txtFind, findView, saveSize, toggleFind;
129                 var strh = "Tj".bounds.height;
130                 var vPad = 10, hPad = 20;
131                 var marg = 10;
132                 var winRect;
133                 var x, y, w, h;
134                 var str;
136                 homeUrl = aHomeUrl;
138                 winRect = Rect(0, 0, 800, (Window.screenBounds.height * 0.8).floor);
139                 winRect = winRect.moveToPoint(winRect.centerIn(Window.screenBounds));
141                 window = Window.new( bounds: winRect ).name_("SuperCollider Help");
143                 toolbar = ();
145                 h = strh + vPad;
146                 x = marg; y = marg;
147                 [[\Back,"<"], [\Forward,">"], [\Reload, "Reload"]].do { |item|
148                         var str = item[1];
149                         var w = str.bounds.width + hPad;
150                         toolbar[item[0]] = Button( window, Rect(x,y,w,h) ).states_([[str]]);
151                         x = x + w + 2;
152                 };
154                 x = x + 10;
155                 str = "Quick lookup:";
156                 w = str.bounds.width + 5;
157                 StaticText(window, Rect(x,y,w,h)).string_(str);
158                 x = x + w;
159                 w = 200;
160                 srchBox = TextField.new( window, Rect(x,y,w,h) ).resize_(1);
161                 if(GUI.current.id == \qt) {
162                         srchBox.toolTip = "Smart quick help lookup. Prefix with # to just search.";
163                 };
164                 srchBox.action = {|x|
165                         if(x.string.notEmpty) {
166                                 this.goTo(if(x.string.first==$#)
167                                         {SCDoc.helpTargetDir++"/Search.html#"++x.string.drop(1)}
168                                         {SCDoc.findHelpFile(x.string)}
169                                 );
170                         }
171                 };
173                 openNewWin = aNewWin;
174                 x = x + w + 10;
175                 if(GUI.current.respondsTo(\checkBox)) {
176                         str = "Open links in new window";
177                         w = str.bounds.width + 50;
178                         CheckBox.new (window, Rect(x, y, w, h) )
179                                 .resize_(1)
180                                 .string_(str)
181                                 .value_(openNewWin)
182                                 .action_({ |b| openNewWin = b.value });
183                 } {
184                         str = "Open links in same window";
185                         w = str.bounds.width + 5;
186                         Button.new( window, Rect(x, y, w, h) )
187                                 .resize_(1)
188                                 .states_([[str],["Open links in new window"]])
189                                 .value_(openNewWin.asInteger)
190                                 .action_({ |b| openNewWin = b.value.asBoolean });
191                 };
193                 x = 0;
194                 y = marg + h + 5;
195                 w = winRect.width;
196                 findView = CompositeView(window, Rect(x,y,w,h+10)).visible_(false).resize_(2);
197                 y = marg;
198                 w = 200;
199                 x = winRect.width - marg - w;
200                 txtFind = TextField.new( findView, Rect(x,y,w,h) ).resize_(3);
201                 str = "Find text in document:";
202                 w = str.bounds.width + 5;
203                 x = x - w - 5;
204                 lblFind = StaticText.new( findView, Rect(x, y, w, h) )
205                         .string_(str)
206                         .resize_(3);
208                 x = 5;
209                 y = marg + h + 5;
210                 w = winRect.width - 10;
211                 h = winRect.height - y - marg;
212                 webView = WebView.new( window, Rect(x,y,w,h) ).resize_(5);
213                 webView.html = "Please wait while initializing Help... (This might take several seconds the first time)";
215                 if(webView.respondsTo(\setFontFamily)) {
216                         webView.setFontFamily(\fixed, Platform.case(
217                                 \osx, { "Monaco" },
218                                 \linux, { "Andale Mono" },
219                                 { "Monospace" }
220                         ))
221                 };
223                 webView.onLoadFinished = {
224                         this.stopAnim;
225                         window.name = "SuperCollider Help: %".format(webView.title);
226                 };
227                 webView.onLoadFailed = { this.stopAnim };
228                 webView.onLinkActivated = {|wv, url|
229                         var newPath, oldPath;
230                         if(openNewWin) {
231                                 #newPath, oldPath = [url,webView.url].collect {|x|
232                                         if(x.notEmpty) {x.findRegexp("(^\\w+://)?([^#]+)(#.*)?")[1..].flop[1][1]}
233                                 };
234                         };
235                         if(newPath!=oldPath) {
236                                 HelpBrowser.new(newWin:true).goTo(url);
237                         } {
238                                 this.goTo(url);
239                         };
240                 };
241                 if(webView.respondsTo(\onReload_)) {
242                         webView.onReload = {|wv, url|
243                                 this.goTo(url);
244                         };
245                 };
247                 toggleFind = {
248                                 if(findView.visible.not) {
249                                         saveSize = webView.bounds;
250                                         h = findView.bounds.height + marg;
251                                         webView.bounds = Rect(saveSize.left,saveSize.top+h,saveSize.width,saveSize.height-h);
252                                         findView.visible = true;
253                                         txtFind.focus;
254                                 } {
255                                         webView.bounds = saveSize;
256                                         findView.visible = false;
257                                 };
258                 };
260                 webView.enterInterpretsSelection = true;
261                 webView.keyDownAction = { arg view, char, mods;
262                         if( (char.ascii == 13) && (mods.isCtrl || mods.isCmd || mods.isShift) ) {
263                                 view.tryPerform(\evaluateJavaScript,"selectLine()");
264                         };
265                 };
266                 window.view.keyDownAction = { arg view, char, mods;
267                         if( ((char.ascii == 6) && mods.isCtrl) || (char == $f && mods.isCmd) ) {
268                                 toggleFind.value;
269                         };
270                         if(char.ascii==27) {
271                                 if(findView.visible) {toggleFind.value};
272                         }
273                 };
275                 toolbar[\Back].action = { this.goBack };
276                 toolbar[\Forward].action = { this.goForward };
277                 toolbar[\Reload].action = { this.goTo( webView.url ) };
278                 txtFind.action = { |x| webView.findText( x.string ); };
279         }
281         openTextFile {|path|
282                 var win, winRect, txt, file, fonts;
283                 path = path.replace("%20"," ").findRegexp("(^\\w+://)?([^#]+)(#.*)?")[1..].flop[1][1];
284                 if(File.exists(path)) {
285                         path.openDocument;
286                 } {
287                         webView.url = SCDoc.helpTargetDir++"/BrokenLink.html#"++path;
288                         window.front;
289                 }
290         }
292         startAnim {
293                 var progress = [">---","->--","-->-","--->"];
294                 animCount = animCount + 1;
295                 if(animCount==1) {
296                         Routine {
297                                 block {|break|
298                                         loop {
299                                                 progress.do {|p|
300                                                         window.name = ("Loading"+p);
301                                                         0.3.wait;
302                                                         if(animCount==0) {break.value};
303                                                 };
304                                         };
305                                 };
306 //                              lblStatus.string_("");
307                         }.play(AppClock);
308                 };
309         }
310         stopAnim {
311                 if(animCount>0) {
312                         animCount = animCount - 1;
313                 };
314         }
318 + Help {
319         *gui {
320                 HelpBrowser.instance.goHome;
321         }