JAL-1807 update
[jalviewjs.git] / site / js / JSmol.js
1 // JSmol.js -- Jmol pure JavaScript version
2 // author: Bob Hanson, hansonr@stolaf.edu       4/16/2012
3 // author: Takanori Nakane biochem_fan 6/12/2012
4
5 // BH 3/28/2015 6:18:33 AM refactoring to generalize for non-Jmol-related SwingJS applications
6 // BH 9/6/2014 5:42:32 PM  two-point gestures broken
7 // BH 5/8/2014 11:16:40 AM j2sPath starting with "/" fails to add idiom
8 // BH 1/16/2014 8:44:03 PM   __execDelayMS = 100; // FF bug when loading into a tab that is not 
9 //                           immediately focused and not using jQuery for adding the applet and having  
10 //                           multiple applets.
11 // BH 12/6/2013 10:12:30 AM adding corejmoljsv.z.js
12 // BH 9/17/2013 10:18:40 AM  file transfer functions moved to JSmolCore 
13 // BH 3/5/2013 9:54:16 PM added support for a cover image: Info.coverImage, coverScript, coverTitle, deferApplet, deferUncover
14 // BH 1/3/2013 4:54:01 AM mouse binding should return false -- see d.bind(...), and d.bind("contextmenu") is not necessary
15
16 // This library requires prior inclusion of 
17
18 //  jQuery 9 or higher
19 //      JSmoljQueryExt.js
20 //      JSmolCore.js
21 //  j2sjmol.js    (Clazz and associated classes)
22
23 // these:
24 //
25 //  JSmolApplet.js
26 //  JSmolApi.js
27 //  JSmolThree.js
28 //  JSmolGLmol.js
29 //  
30 //  are optional 
31
32 ;(function (Jmol) {
33
34         Jmol._isAsync = false; // testing only
35         Jmol._asyncCallbacks = {};
36         
37         Jmol._coreFiles = []; // required for package.js
38
39
40 ///////////////////
41 // This section provides an asynchronous loading sequence
42 //
43
44 // methods and fields starting with double underscore are private to this .js file
45
46   var __clazzLoaded = false;
47         var __execLog = [];
48         var __execStack = [];
49         var __execTimer = 0;
50         var __coreSet = [];
51         var __coreMore = [];
52         var __execDelayMS = 100; // must be > 55 ms for FF
53
54         var __nextExecution = function(trigger) {
55     arguments.length || (trigger = true);
56                 delete __execTimer;
57                 var es = __execStack;
58                 var e;
59                 while (es.length > 0 && (e = es[0])[4] == "done")
60                         es.shift();
61                 if (es.length == 0)
62                         return;
63                 if (!Jmol._isAsync && !trigger) {
64                         setTimeout(__nextExecution,10)
65                         return;
66                 }
67                 e.push("done");
68                 var s = "JSmol exec " + e[0]._id + " " + e[3] + " " + e[2];
69                 if (self.System)
70                         System.out.println(s);
71                         //alert(s)
72                 if (self.console)console.log(s + " -- OK")
73                 __execLog.push(s);
74                 e[1](e[0],e[2]);        
75         };
76
77         var __loadClazz = function(applet) {
78                 if (!__clazzLoaded) {
79                         __clazzLoaded = true;
80                         // create the Clazz object
81                         LoadClazz();
82                         if (applet._noMonitor)
83                                 Clazz._LoaderProgressMonitor.showStatus = function() {}
84                         LoadClazz = null;
85       if (applet.__Info.uncompressed)
86         Clazz.loadClass(); // for now; allows for no compression 
87                         Clazz._Loader.onGlobalLoaded = function (file) {
88                          // not really.... just nothing more yet to do yet
89                                 Clazz._LoaderProgressMonitor.showStatus("Application loaded.", true);
90                                 if (!Jmol._debugCode || !Jmol.haveCore) {
91                                         Jmol.haveCore = true;
92                                         __nextExecution();
93                                 }
94                         };
95                   // load package.js and j2s/core/core.z.js
96                         Clazz._Loader.loadPackageClasspath("java", null, true, __nextExecution);
97                         return;
98                 }
99                 __nextExecution();
100         };
101
102         var __loadClass = function(applet, javaClass) {
103                 Clazz._Loader.loadClass(javaClass, function() {__nextExecution()});
104         };
105
106         Jmol.showExecLog = function() { return __execLog.join("\n") }; 
107
108         Jmol._addExec = function(e) {
109     e[1] || (e[1] = __loadClass);
110                 var s = "JSmol load " + e[0]._id + " " + e[3];
111                 if (self.console)console.log(s + "...")
112                 __execLog.push(s);   
113                 __execStack.push(e);
114         }
115
116         Jmol._addCoreFile = function(type, path, more) {
117   
118     // BH 3/15: idea here is that when both Jmol and JSV are present, 
119     // we want to load a common core file -- jmoljsv.z.js --
120     // instead of just one. Otherwise we do a lot of duplication.
121     // It is not clear how this would play with other concurrent
122     // apps. So this will take some thinking. But the basic idea is that
123     // core file to load is 
124      
125     type = type.toLowerCase().split(".")[0]; // package name only 
126
127     // return if type is already part of the set.    
128                 if (__coreSet.join("").indexOf(type) >= 0) return;
129     
130     // create a concatenated lower-case name for a core file that includes
131     // all Java applets on the page
132     
133                 __coreSet.push(type);
134                 __coreSet.sort();
135                 Jmol._coreFiles = [path + "/core/core" + __coreSet.join("") + ".z.js" ];
136                 if (more && (more = more.split(" ")))
137                         for (var i = 0; i < more.length; i++)
138                                 if (__coreMore.join("").indexOf(more[i]) < 0)
139                                         __coreMore.push(path + "/core/core" + more[i] + ".z.js")
140                 for (var i = 0; i < __coreMore.length; i++)
141                         Jmol._coreFiles.push(__coreMore[i]);
142         }               
143
144         Jmol._Canvas2D = function(id, Info, type, checkOnly){
145                 // type: Jmol or JSV
146                 this._uniqueId = ("" + Math.random()).substring(3);
147                 this._id = id;
148                 this._is2D = true;
149                 this._isJava = false;
150                 this._jmolType = "Jmol._Canvas2D (" + type + ")";
151     this._isLayered = Info._isLayered || false;
152     this._isSwing = Info._isSwing || false;
153     this._isJSV = Info._isJSV || false;
154     this._isAstex = Info._isAstex || false;            
155     this._platform = Info._platform || "";
156                 if (checkOnly)
157                         return this;
158                 window[id] = this;
159                 this._createCanvas(id, Info);
160                 if (!Jmol._document || this._deferApplet)
161                         return this;
162                 this._init();
163                 return this;
164         };
165
166         Jmol._setAppletParams = function(availableParams, params, Info, isHashtable) {
167                 for (var i in Info)
168                         if(!availableParams || availableParams.indexOf(";" + i.toLowerCase() + ";") >= 0){
169                                 if (Info[i] == null || i == "language" && !Jmol.featureDetection.supportsLocalization())
170                                         continue;
171                                 if (isHashtable)
172                                         params.put(i, (Info[i] === true ? Boolean.TRUE: Info[i] === false ? Boolean.FALSE : Info[i]))
173                                 else
174                                         params[i] = Info[i];
175                         }
176         }     
177          
178         Jmol._jsSetPrototype = function(proto) {
179                 proto._init = function() {
180                         this._setupJS();
181                         this._showInfo(true); 
182                         if (this._disableInitialConsole)
183                                 this._showInfo(false);
184                 };
185
186                 proto._createCanvas = function(id, Info, glmol) {
187                         Jmol._setObject(this, id, Info);
188                         if (glmol) {
189                                 this._GLmol = glmol;
190                                 this._GLmol.applet = this;
191                                 this._GLmol.id = this._id;
192                         }      
193                         var t = Jmol._getWrapper(this, true);
194                         if (this._deferApplet) {
195                         } else if (Jmol._document) {
196                                 Jmol._documentWrite(t);
197                                 this._newCanvas(false);                                 
198                                 t = "";
199                         } else {
200                                 this._deferApplet = true;
201                                 t += '<script type="text/javascript">' + id + '._cover(false)</script>';
202                         }
203                         t += Jmol._getWrapper(this, false);
204                         if (Info.addSelectionOptions)
205                                 t += Jmol._getGrabberOptions(this);
206                         if (Jmol._debugAlert && !Jmol._document)
207                                 alert(t);
208                         this._code = Jmol._documentWrite(t);
209                 };
210
211                 proto._newCanvas = function(doReplace) {
212                         if (this._is2D)
213                                 this._createCanvas2d(doReplace);
214                         else
215                                 this._GLmol.create();
216                 };
217
218 //////// swingjs.api.HTML5Applet interface    
219     proto._getHtml5Canvas = function() { return this._canvas }; 
220     proto._getWidth = function() { return this._canvas.width }; 
221     proto._getHeight = function() { return this._canvas.height };
222     proto._getContentLayer = function() { return Jmol.$(this, "contentLayer")[0] };
223     proto._repaintNow = function() { Jmol._repaint(this, false) }; 
224 ////////
225
226
227                 proto._createCanvas2d = function(doReplace) {
228                         var container = Jmol.$(this, "appletdiv");
229                         //if (doReplace) {
230       
231                         try {
232                         container[0].removeChild(this._canvas);
233                         if (this._canvas.frontLayer)
234                                 container[0].removeChild(this._canvas.frontLayer);
235                         if (this._canvas.rearLayer)
236                                 container[0].removeChild(this._canvas.rearLayer);
237                         if (this._canvas.contentLayer)
238                                 container[0].removeChild(this._canvas.contentLayer);
239                         Jmol._jsUnsetMouse(this._mouseInterface);
240                         } catch (e) {}
241                         //}
242                         var w = Math.round(container.width());
243                         var h = Math.round(container.height());
244                         var canvas = document.createElement( 'canvas' );
245                         canvas.applet = this;
246                         this._canvas = canvas;
247                         canvas.style.width = "100%";
248                         canvas.style.height = "100%";
249                         canvas.width = w;
250                         canvas.height = h; // w and h used in setScreenDimension
251                         canvas.id = this._id + "_canvas2d";
252                         container.append(canvas);
253                         Jmol._$(canvas.id).css({"z-index":Jmol._getZ(this, "main")});
254                         if (this._isLayered){
255                                 var img = document.createElement("div");
256                                 canvas.contentLayer = img;
257                                 img.id = this._id + "_contentLayer";
258                                 container.append(img);
259                                 Jmol._$(img.id).css({zIndex:Jmol._getZ(this, "image"),position:"absolute",left:"0px",top:"0px",
260         width:(this._isSwing ? w : 0) + "px", height:(this._isSwing ? h : 0) +"px", overflow:"hidden"});
261         if (this._isSwing) {
262                 var d = document.createElement("div");
263           d.id = this._id + "_swingdiv";
264                 Jmol._$(this._id + "_appletinfotablediv").append(d);
265                                   Jmol._$(d.id).css({zIndex:Jmol._getZ(this, "rear"),position:"absolute",left:"0px",top:"0px", width:w +"px", height:h+"px", overflow:"hidden"});
266                                 this._mouseInterface = canvas.contentLayer;
267           canvas.contentLayer.applet = this;
268         } else {
269                                 this._mouseInterface = this._getLayer("front", container, w, h, false);
270         }
271                                 //this._getLayer("rear", container, w, h, true);
272                                 //Jmol._$(canvas.id).css({background:"rgb(0,0,0,0.001)", "z-index":Jmol._z.main}); 
273                         } else {
274                                 this._mouseInterface = canvas;
275                         }
276                         Jmol._jsSetMouse(this._mouseInterface);
277                 }
278     
279     proto._getLayer = function(name, container, w, h, isOpaque) {
280                 var c = document.createElement("canvas");
281                         this._canvas[name + "Layer"] = c;
282                         c.style.width = "100%";
283                         c.style.height = "100%";
284                         c.id = this._id + "_" + name + "Layer";
285                         c.width = w;
286                         c.height = h; // w and h used in setScreenDimension
287                         container.append(c);
288                         c.applet = this;
289                         Jmol._$(c.id).css({background:(isOpaque ? "rgb(0,0,0,1)" : "rgb(0,0,0,0.001)"), "z-index": Jmol._getZ(this,name),position:"absolute",left:"0px",top:"0px",overflow:"hidden"});
290                         return c;       
291     }
292     
293     
294                 proto._setupJS = function() {
295                         window["j2s.lib"] = {
296                                 base : this._j2sPath + "/",
297                                 alias : ".",
298                                 console : this._console,
299                                 monitorZIndex : Jmol._getZ(this, "monitorZIndex")
300                         };
301                         var isFirst = (__execStack.length == 0);
302                         if (isFirst)
303                                 Jmol._addExec([this, __loadClazz, null, "loadClazz"]);
304       this._addCoreFiles();
305                         Jmol._addExec([this, this.__startAppletJS, null, "start applet"])
306                         this._isSigned = true; // access all files via URL hook
307                         this._ready = false; 
308                         this._applet = null;
309                         this._canScript = function(script) {return true;};
310                         this._savedOrientations = [];
311                         __execTimer && clearTimeout(__execTimer);
312                         __execTimer = setTimeout(__nextExecution, __execDelayMS);
313                 };
314
315                 proto.__startAppletJS = function(applet) {
316                         if (Jmol._version.indexOf("$Date: ") == 0)
317                                 Jmol._version = (Jmol._version.substring(7) + " -").split(" -")[0] + " (JSmol/j2s)"
318                         var viewerOptions = Clazz._4Name("java.util.Hashtable").newInstance();
319                         Jmol._setAppletParams(applet._availableParams, viewerOptions, applet.__Info, true);
320                         viewerOptions.put("appletReadyCallback","Jmol._readyCallback");
321                         viewerOptions.put("applet", true);
322                         viewerOptions.put("name", applet._id);// + "_object");
323                         viewerOptions.put("syncId", Jmol._syncId);
324                         if (Jmol._isAsync)
325                                 viewerOptions.put("async", true);
326                         if (applet._color) 
327                                 viewerOptions.put("bgcolor", applet._color);
328                         if (applet._startupScript)
329                                 viewerOptions.put("script", applet._startupScript)
330                         if (Jmol._syncedApplets.length)
331                                 viewerOptions.put("synccallback", "Jmol._mySyncCallback");
332                         viewerOptions.put("signedApplet", "true");
333                         viewerOptions.put("platform", applet._platform);
334                         if (applet._is2D)
335                                 viewerOptions.put("display",applet._id + "_canvas2d");
336
337                         // viewerOptions.put("repaintManager", "J.render");
338                         viewerOptions.put("documentBase", document.location.href);
339                         var codePath = applet._j2sPath + "/";
340       
341                         if (codePath.indexOf("://") < 0) {
342                                 var base = document.location.href.split("#")[0].split("?")[0].split("/");
343                                 if (codePath.indexOf("/") == 0)
344                                         base = [base[0], codePath.substring(1)];
345                                 else
346                                         base[base.length - 1] = codePath;
347                                 codePath = base.join("/");
348                         }
349                         viewerOptions.put("codePath", codePath);
350                         Jmol._registerApplet(applet._id, applet);
351                         try {
352                                 applet._newApplet(viewerOptions);
353                         } catch (e) {
354                                 System.out.println((Jmol._isAsync ? "normal async abort from " : "") + e);
355                                 return;
356                         }
357       
358                         applet._jsSetScreenDimensions();
359                         __nextExecution();
360                 };
361
362     if (!proto._restoreState)
363                 proto._restoreState = function(clazzName, state) {
364         // applet-dependent
365                   }
366         
367                 proto._jsSetScreenDimensions = function() {
368                                 if (!this._appletPanel)return
369                                 // strangely, if CTRL+/CTRL- are used repeatedly, then the
370                                 // applet div can be not the same size as the canvas if there
371                                 // is a border in place.
372                                 var d = Jmol._getElement(this, (this._is2D ? "canvas2d" : "canvas"));
373                                 this._appletPanel.setScreenDimension(d.width, d.height);
374                 };
375
376                 proto._show = function(tf) {
377                         Jmol.$setVisible(Jmol.$(this,"appletdiv"), tf);
378                         if (tf)
379                                 Jmol._repaint(this, true);
380                 };
381
382                 proto._canScript = function(script) {return true};
383
384 //              proto._delay = function(eval, sc, millis) {
385 //    alert("_delay???")
386 //              // does not take into account that scripts may be added after this and
387 //              // need to be cached.
388 //                      this._delayID = setTimeout(function(){eval.resumeEval(sc,false)}, millis);              
389 //              }
390 /*
391                 proto._createDomNode = function(id, data) { // moved to org.jmol.adapter.readers.xml.XmlReader.java
392                         id = this._id + "_" + id;
393                         var d = document.getElementById(id);
394                         if (d)
395                                 document.body.removeChild(d);
396                         if (!data)
397                                 return;
398                         if (data.indexOf("<?") == 0)
399                                 data = data.substring(data.indexOf("<", 1));
400                         if (data.indexOf("/>") >= 0) {
401                                 // no doubt there is a more efficient way to do this.
402                                 // Firefox, at least, does not recognize "/>" in HTML blocks
403                                 // that are added this way.
404                                 var D = data.split("/>");
405                                 for (var i = D.length - 1; --i >= 0;) {
406                                         var s = D[i];
407                                         var pt = s.lastIndexOf("<") + 1;
408                                         var pt2 = pt;
409                                         var len = s.length;
410                                         var name = "";
411                                         while (++pt2 < len) {
412                                                 if (" \t\n\r".indexOf(s.charAt(pt2))>= 0) {
413                                                         var name = s.substring(pt, pt2);
414                                                         D[i] = s + "></"+name+">";
415                                                         break;
416                                                 }               
417                                         }
418                                 }
419                                 data = D.join('');
420                         }
421                         d = document.createElement("_xml")
422                         d.id = id;
423                         d.innerHTML = data;
424                         d.style.display = "none";
425                         document.body.appendChild(d);
426                         return d;
427                 }               
428 */
429                 proto.equals = function(a) { return this == a };
430                 proto.clone = function() { return this };
431                 proto.hashCode = function() { return parseInt(this._uniqueId) };  
432
433
434                 proto._processGesture = function(touches) {
435                         return this._appletPanel.processTwoPointGesture(touches);
436                 }
437
438                 proto._processEvent = function(type, xym) {
439                         this._appletPanel.processMouseEvent(type,xym[0],xym[1],xym[2],System.currentTimeMillis());
440                 }
441
442                 proto._resize = function() {
443                         var s = "__resizeTimeout_" + this._id;
444                         // only at end
445                         if (Jmol[s])
446                                 clearTimeout(Jmol[s]);
447                         var me = this;
448                         Jmol[s] = setTimeout(function() {Jmol._repaint(me, true);Jmol[s]=null}, 100);
449                 }
450
451                 return proto;
452         };
453
454         Jmol._repaint = function(applet, asNewThread) {
455                 // asNewThread: true is from RepaintManager.repaintNow()
456                 // false is from Repaintmanager.requestRepaintAndWait()
457                 // called from apiPlatform Display.repaint()
458
459                 //alert("_repaint " + Clazz.getStackTrace())
460                 if (!applet || !applet._appletPanel)return;
461
462                 // asNewThread = false;
463                 var container = Jmol.$(applet, "appletdiv");
464                 var w = Math.round(container.width());
465                 var h = Math.round(container.height());
466                 if (applet._is2D && (applet._canvas.width != w || applet._canvas.height != h)) {
467                         applet._newCanvas(true);
468                         applet._appletPanel.setDisplay(applet._canvas);
469                 }
470                 applet._appletPanel.setScreenDimension(w, h);
471     var f = function(){
472       if (applet._appletPanel.paint)
473         applet._appletPanel.paint(null);
474       else
475         applet._appletPanel.update(null)
476     };
477                 if (asNewThread) {
478                         setTimeout(f);
479                 } else {
480       f();
481                 }
482                 // System.out.println(applet._appletPanel.getFullName())
483         }
484
485         Jmol._getHiddenCanvas = function(applet, id, width, height, forceNew) {
486                 id = applet._id + "_" + id;
487                 var d = document.getElementById(id);
488                 if (d && forceNew) {
489                         d = null;
490                 }
491                 if (!d)
492                         d = document.createElement( 'canvas' );
493                         // for some reason both these need to be set, or maybe just d.width?
494                 d.width = d.style.width = width;
495                 d.height = d.style.height = height;
496                 // d.style.display = "none";
497                 if (d.id != id) {
498                         d.id = id;
499                 }
500                 return d;
501         }
502
503         Jmol._loadImage = function(platform, echoNameAndPath, bytes, fOnload, image) {
504         // bytes would be from a ZIP file -- will have to reflect those back from
505         // server as an image after conversion to base64
506         // ah, but that's a problem, because that image would be needed to be
507         // posted, but you can't post an image call.
508         // so we would have to go with <image data:> which does not work in all
509         // browsers. Hmm.
510
511                 var path = echoNameAndPath[1];
512
513                 if (image == null) {
514                         var image = new Image();
515                         image.onload = function() {Jmol._loadImage(platform, echoNameAndPath, null, fOnload, image)};
516
517                         if (bytes != null) {      
518                                 bytes = J.io.Base64.getBase64(bytes).toString();      
519                                 var filename = path.substring(url.lastIndexOf("/") + 1);                                    
520                                 var mimetype = (filename.indexOf(".png") >= 0 ? "image/png" : filename.indexOf(".jpg") >= 0 ? "image/jpg" : "");
521                                  // now what?
522                         }
523                         image.src = path;
524                         return true; // as far as we can tell!
525                 }
526                 var width = image.width;
527                 var height = image.height; 
528                 var id = "echo_" + echoNameAndPath[0];  
529                 var canvas = Jmol._getHiddenCanvas(platform.vwr.html5Applet, id, width, height, true);
530                 canvas.imageWidth = width;
531                 canvas.imageHeight = height;
532                 canvas.id = id;
533                 canvas.image=image;
534                 Jmol._setCanvasImage(canvas, width, height);
535                 // return a null canvas and the error in path if there is a problem
536                 fOnload(canvas,path);
537         };
538
539         Jmol._setCanvasImage = function(canvas, width, height) {
540     // called from org.jmol.awtjs2d.Platform
541                 canvas.buf32 = null;
542                 canvas.width = width;
543                 canvas.height = height;
544                 canvas.getContext("2d").drawImage(canvas.image, 0, 0, width, height);
545         };
546
547 })(Jmol);