scide: LookupDialog - redo lookup on classes after partial lookup
[supercollider.git] / SCClassLibrary / Common / GUI / HelpBrowser.sc
blob349b11e8fc99c3297b626de7acb2bfc7942e7b5f
1 HelpBrowser {
2         classvar singleton;
3         classvar <>defaultHomeUrl;
4         classvar <>openNewWindows = false;
6         var <>homeUrl;
7         var <window;
8         var webView;
9         var loading = false;
10         var srchBox;
11         var openNewWin;
12         var rout;
14         *initClass {
15                 StartUp.add {
16                         NotificationCenter.register(SCDoc, \didIndexAllDocs, 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                 };
28                 ^singleton;
29         }
31         *new { arg aHomeUrl, newWin;
32                 if( aHomeUrl.isNil ) {
33                         aHomeUrl = defaultHomeUrl ?? { SCDoc.helpTargetDir ++ "/Help.html" };
34                 };
35                 ^super.new.init( aHomeUrl, newWin ?? { openNewWindows } );
36         }
38         *goTo {|url|
39                 this.front.goTo(url);
40         }
42         *front {
43                 var w = if(openNewWindows,{this.new},{this.instance});
44                 w.window.front;
45                 ^w;
46         }
48         *openBrowsePage {|category|
49                 category = if(category.notNil) {"#"++category} {""};
50                 this.goTo(SCDoc.helpTargetDir++"/Browse.html"++category);
51         }
52         *openSearchPage {|text|
53                 text = if(text.notNil) {"#"++text} {""};
54                 this.goTo(SCDoc.helpTargetDir++"/Search.html"++text);
55         }
56         *openHelpFor {|text|
57                 var w = this.front;
58                 { w.startAnim; w.goTo(SCDoc.findHelpFile(text)) }.fork(AppClock);
59         }
60         *openHelpForMethod {|method|
61                 var cls = method.ownerClass;
62                 var met = method.name.asString;
63                 if(cls.isMetaClass) {
64                         cls = cls.name.asString.drop(5);
65                         met = "*"++met;
66                 } {
67                         cls = cls.name.asString;
68                         met = "-"++met;
69                 };
70                 this.goTo(SCDoc.helpTargetDir+/+"Classes"+/+cls++".html#"++met);
71         }
72         *getOldWrapUrl {|url|
73                 var c;
74                 ^("file://" ++ SCDoc.helpTargetDir +/+ "OldHelpWrapper.html#"++url++"?"++
75                 SCDoc.helpTargetDir +/+ if((c=url.basename.split($.).first).asSymbol.asClass.notNil)
76                         {"Classes" +/+ c ++ ".html"}
77                         {"Guides/WritingHelp.html"})
78         }
79         cmdPeriod { rout.play(AppClock) }
80         goTo {|url, brokenAction|
81                 var newPath, oldPath, plainTextExts = #[".sc",".scd",".txt",".schelp",".rtf"];
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(url.beginsWith("sc://")) {
102                                         url = SCDoc.findHelpFile(newPath);
103                                 } {
104                                         if(
105                                                 /*
106                                                 // this didn't work for quarks due to difference between registered old help path and the quarks symlink in Extensions.
107                                                 // we could use File.realpath(path) below but that would double the execution time,
108                                                 // so let's just assume any local file outside helpTargetDir is an old helpfile.
109                                                 block{|break|
110                                                 Help.do {|key, path|
111                                                 if(url.endsWith(path)) {
112                                                 break.value(true)
113                                                 }
114                                                 }; false
115                                                 }*/
116                                                 newPath.beginsWith(SCDoc.helpTargetDir).not and: { url.beginsWith("file://") }
117                                         ) {
118                                                 url = HelpBrowser.getOldWrapUrl(url)
119                                         };
120                                 };
121                                 webView.url = url;
122                                 // needed since onLoadFinished is not called if the path did not change:
123                                 if(newPath == oldPath) {webView.onLoadFinished.value};
124                                 webView.focus;
125                         } {|err|
126                                 webView.html = err.errorString;
127                                 err.throw;
128                         };
129                         CmdPeriod.remove(this);
130                         rout = nil;
131                 }.play(AppClock);
132                 CmdPeriod.add(this);
133         }
135         goHome { this.goTo(homeUrl); }
137         goBack { webView.back; }
139         goForward { webView.forward; }
141 /* ------------------------------ private ------------------------------ */
143         init { arg aHomeUrl, aNewWin;
144                 var toolbar;
145                 var lblFind, txtFind, findView, saveSize, toggleFind;
146                 var strh = "Tj".bounds.height;
147                 var vPad = 10, hPad = 20;
148                 var marg = 10;
149                 var winRect;
150                 var x, y, w, h;
151                 var str;
153                 homeUrl = aHomeUrl;
155                 winRect = Rect(0, 0, 800, (Window.screenBounds.height * 0.8).floor);
156                 winRect = winRect.moveToPoint(winRect.centerIn(Window.screenBounds));
158                 window = Window.new( bounds: winRect ).name_("SuperCollider Help");
160                 window.onClose = {
161                         this.stopAnim;
162                         if(singleton == this) {
163                                 singleton = nil;
164                         };
165                 };
167                 toolbar = ();
169                 h = strh + vPad;
170                 x = marg; y = marg;
171                 [[\Back,"<"], [\Forward,">"], [\Reload, "Reload"]].do { |item|
172                         var str = item[1];
173                         var w = str.bounds.width + hPad;
174                         toolbar[item[0]] = Button( window, Rect(x,y,w,h) ).states_([[str]]);
175                         x = x + w + 2;
176                 };
178                 x = x + 10;
179                 str = "Quick lookup:";
180                 w = str.bounds.width + 5;
181                 StaticText(window, Rect(x,y,w,h)).string_(str);
182                 x = x + w;
183                 w = 200;
184                 srchBox = TextField.new( window, Rect(x,y,w,h) ).resize_(1);
185                 if(GUI.current.id == \qt) {
186                         srchBox.toolTip = "Smart quick help lookup. Prefix with # to just search.";
187                 };
188                 srchBox.action = {|x|
189                         if(x.string.notEmpty) {
190                                 this.goTo(if(x.string.first==$#)
191                                         {SCDoc.helpTargetDir++"/Search.html#"++x.string.drop(1)}
192                                         {SCDoc.findHelpFile(x.string)}
193                                 );
194                         }
195                 };
197                 openNewWin = aNewWin;
198                 x = x + w + 10;
199                 if(GUI.current.respondsTo(\checkBox)) {
200                         str = "Open links in new window";
201                         w = str.bounds.width + 50;
202                         CheckBox.new (window, Rect(x, y, w, h) )
203                                 .resize_(1)
204                                 .string_(str)
205                                 .value_(openNewWin)
206                                 .action_({ |b| openNewWin = b.value });
207                 } {
208                         str = "Open links in same window";
209                         w = str.bounds.width + 5;
210                         Button.new( window, Rect(x, y, w, h) )
211                                 .resize_(1)
212                                 .states_([[str],["Open links in new window"]])
213                                 .value_(openNewWin.asInteger)
214                                 .action_({ |b| openNewWin = b.value.asBoolean });
215                 };
217                 x = 0;
218                 y = marg + h + 5;
219                 w = winRect.width;
220                 findView = CompositeView(window, Rect(x,y,w,h+10)).visible_(false).resize_(2);
221                 y = marg;
222                 w = 200;
223                 x = winRect.width - marg - w;
224                 txtFind = TextField.new( findView, Rect(x,y,w,h) ).resize_(3);
225                 str = "Find text in document:";
226                 w = str.bounds.width + 5;
227                 x = x - w - 5;
228                 lblFind = StaticText.new( findView, Rect(x, y, w, h) )
229                         .string_(str)
230                         .resize_(3);
232                 x = 5;
233                 y = marg + h + 5;
234                 w = winRect.width - 10;
235                 h = winRect.height - y - marg;
236                 webView = WebView.new( window, Rect(x,y,w,h) ).resize_(5);
237                 webView.html = "Please wait while initializing Help... (This might take several seconds the first time)";
239                 if(webView.respondsTo(\setFontFamily)) {
240                         webView.setFontFamily(\fixed, Platform.case(
241                                 \osx, { "Monaco" },
242                                 \linux, { "Andale Mono" },
243                                 { "Monospace" }
244                         ))
245                 };
247                 webView.onLoadFinished = {
248                         this.stopAnim;
249                         window.name = "SuperCollider Help: %".format(webView.title);
250                 };
251                 webView.onLoadFailed = { this.stopAnim };
252                 webView.onLinkActivated = {|wv, url|
253                         var newPath, oldPath;
254                         if(openNewWin) {
255                                 #newPath, oldPath = [url,webView.url].collect {|x|
256                                         if(x.notEmpty) {x.findRegexp("(^\\w+://)?([^#]+)(#.*)?")[1..].flop[1][1]}
257                                 };
258                         };
259                         if(newPath!=oldPath) {
260                                 HelpBrowser.new(newWin:true).goTo(url);
261                         } {
262                                 this.goTo(url);
263                         };
264                 };
265                 if(webView.respondsTo(\onReload_)) {
266                         webView.onReload = {|wv, url|
267                                 if(WebView.implClass.respondsTo(\clearCache)) {
268                                         WebView.clearCache;
269                                 };
270                                 this.goTo(url);
271                         };
272                 };
273                 if(webView.respondsTo(\onJavaScriptMsg_)) {
274                         webView.onJavaScriptMsg = {|wv, err, type|
275                                 "JavaScript %: %".format(if(type==0,"Error","Message"),err).postln;
276                         };
277                 };
279                 toggleFind = {
280                                 if(findView.visible.not) {
281                                         saveSize = webView.bounds;
282                                         h = findView.bounds.height + marg;
283                                         webView.bounds = Rect(saveSize.left,saveSize.top+h,saveSize.width,saveSize.height-h);
284                                         findView.visible = true;
285                                         txtFind.focus;
286                                 } {
287                                         webView.bounds = saveSize;
288                                         findView.visible = false;
289                                 };
290                 };
292                 webView.enterInterpretsSelection = true;
293                 webView.keyDownAction = { arg view, char, mods;
294                         if( (char.ascii == 13) && (mods.isCtrl || mods.isCmd || mods.isShift) ) {
295                                 view.tryPerform(\evaluateJavaScript,"selectLine()");
296                         };
297                 };
298                 window.view.keyDownAction = { arg view, char, mods, uni, kcode, key;
299                         if( ((key == 70) && mods.isCtrl) || (char == $f && mods.isCmd) ) {
300                                 toggleFind.value;
301                         };
302                         if(char.ascii==27) {
303                                 if(findView.visible) {toggleFind.value};
304                         }
305                 };
307                 toolbar[\Back].action = { this.goBack };
308                 toolbar[\Forward].action = { this.goForward };
309                 toolbar[\Reload].action = { this.goTo( webView.url ) };
310                 if(GUI.id === \cocoa) {
311                         txtFind.action = { |x| webView.focus; AppClock.sched(0, {webView.findText( x.string );}) };
312                 } {
313                         txtFind.action = { |x| webView.findText( x.string ) };
314                 };
315         }
317         openTextFile {|path|
318                 var win, winRect, txt, file, fonts;
319                 path = path.replace("%20"," ").findRegexp("(^\\w+://)?([^#]+)(#.*)?")[1..].flop[1][1];
320                 if(File.exists(path)) {
321                         path.openDocument;
322                 } {
323                         webView.url = SCDoc.helpTargetDir++"/BrokenLink.html#"++path;
324                         window.front;
325                 }
326         }
328         startAnim {
329                 var progress = [">---","->--","-->-","--->"];
330                 if(loading.not) {
331                         loading = true;
332                         Routine {
333                                 block {|break|
334                                         loop {
335                                                 progress.do {|p|
336                                                         window.name = ("Loading"+p);
337                                                         0.3.wait;
338                                                         if(loading.not) {break.value};
339                                                 };
340                                         };
341                                 };
342 //                              lblStatus.string_("");
343                         }.play(AppClock);
344                 };
345         }
346         stopAnim {
347                 loading = false;
348         }