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