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
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
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
16 // This library requires prior inclusion of
21 // j2sjmol.js (Clazz and associated classes)
34 Jmol._isAsync = false; // testing only
35 Jmol._asyncCallbacks = {};
37 Jmol._coreFiles = []; // required for package.js
41 // This section provides an asynchronous loading sequence
44 // methods and fields starting with double underscore are private to this .js file
46 var __clazzLoaded = false;
52 var __execDelayMS = 100; // must be > 55 ms for FF
54 var __nextExecution = function(trigger) {
55 arguments.length || (trigger = true);
59 while (es.length > 0 && (e = es[0])[4] == "done")
63 if (!Jmol._isAsync && !trigger) {
64 setTimeout(__nextExecution,10)
68 var s = "JSmol exec " + e[0]._id + " " + e[3] + " " + e[2];
70 System.out.println(s);
72 if (self.console)console.log(s + " -- OK")
77 var __loadClazz = function(applet) {
80 // create the Clazz object
82 if (applet._noMonitor)
83 Clazz._LoaderProgressMonitor.showStatus = function() {}
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) {
95 // load package.js and j2s/core/core.z.js
96 Clazz._Loader.loadPackageClasspath("java", null, true, __nextExecution);
102 var __loadClass = function(applet, javaClass) {
103 Clazz._Loader.loadClass(javaClass, function() {__nextExecution()});
106 Jmol.showExecLog = function() { return __execLog.join("\n") };
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 + "...")
116 Jmol._addCoreFile = function(type, path, more) {
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
125 type = type.toLowerCase().split(".")[0]; // package name only
127 // return if type is already part of the set.
128 if (__coreSet.join("").indexOf(type) >= 0) return;
130 // create a concatenated lower-case name for a core file that includes
131 // all Java applets on the page
133 __coreSet.push(type);
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]);
144 Jmol._Canvas2D = function(id, Info, type, checkOnly){
146 this._uniqueId = ("" + Math.random()).substring(3);
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 || "";
159 this._createCanvas(id, Info);
160 if (!Jmol._document || this._deferApplet)
166 Jmol._setAppletParams = function(availableParams, params, Info, isHashtable) {
168 if(!availableParams || availableParams.indexOf(";" + i.toLowerCase() + ";") >= 0){
169 if (Info[i] == null || i == "language" && !Jmol.featureDetection.supportsLocalization())
172 params.put(i, (Info[i] === true ? Boolean.TRUE: Info[i] === false ? Boolean.FALSE : Info[i]))
178 Jmol._jsSetPrototype = function(proto) {
179 proto._init = function() {
181 this._showInfo(true);
182 if (this._disableInitialConsole)
183 this._showInfo(false);
186 proto._createCanvas = function(id, Info, glmol) {
187 Jmol._setObject(this, id, Info);
190 this._GLmol.applet = this;
191 this._GLmol.id = this._id;
193 var t = Jmol._getWrapper(this, true);
194 if (this._deferApplet) {
195 } else if (Jmol._document) {
196 Jmol._documentWrite(t);
197 this._newCanvas(false);
200 this._deferApplet = true;
201 t += '<script type="text/javascript">' + id + '._cover(false)</script>';
203 t += Jmol._getWrapper(this, false);
204 if (Info.addSelectionOptions)
205 t += Jmol._getGrabberOptions(this);
206 if (Jmol._debugAlert && !Jmol._document)
208 this._code = Jmol._documentWrite(t);
211 proto._newCanvas = function(doReplace) {
213 this._createCanvas2d(doReplace);
215 this._GLmol.create();
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) };
227 proto._createCanvas2d = function(doReplace) {
228 var container = Jmol.$(this, "appletdiv");
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);
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%";
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"});
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;
269 this._mouseInterface = this._getLayer("front", container, w, h, false);
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});
274 this._mouseInterface = canvas;
276 Jmol._jsSetMouse(this._mouseInterface);
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";
286 c.height = h; // w and h used in setScreenDimension
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"});
294 proto._setupJS = function() {
295 window["j2s.lib"] = {
296 base : this._j2sPath + "/",
298 console : this._console,
299 monitorZIndex : Jmol._getZ(this, "monitorZIndex")
301 var isFirst = (__execStack.length == 0);
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
309 this._canScript = function(script) {return true;};
310 this._savedOrientations = [];
311 __execTimer && clearTimeout(__execTimer);
312 __execTimer = setTimeout(__nextExecution, __execDelayMS);
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);
325 viewerOptions.put("async", true);
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);
335 viewerOptions.put("display",applet._id + "_canvas2d");
337 // viewerOptions.put("repaintManager", "J.render");
338 viewerOptions.put("documentBase", document.location.href);
339 var codePath = applet._j2sPath + "/";
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)];
346 base[base.length - 1] = codePath;
347 codePath = base.join("/");
349 viewerOptions.put("codePath", codePath);
350 Jmol._registerApplet(applet._id, applet);
352 applet._newApplet(viewerOptions);
354 System.out.println((Jmol._isAsync ? "normal async abort from " : "") + e);
358 applet._jsSetScreenDimensions();
362 if (!proto._restoreState)
363 proto._restoreState = function(clazzName, state) {
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);
376 proto._show = function(tf) {
377 Jmol.$setVisible(Jmol.$(this,"appletdiv"), tf);
379 Jmol._repaint(this, true);
382 proto._canScript = function(script) {return true};
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);
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);
395 document.body.removeChild(d);
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;) {
407 var pt = s.lastIndexOf("<") + 1;
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+">";
421 d = document.createElement("_xml")
424 d.style.display = "none";
425 document.body.appendChild(d);
429 proto.equals = function(a) { return this == a };
430 proto.clone = function() { return this };
431 proto.hashCode = function() { return parseInt(this._uniqueId) };
434 proto._processGesture = function(touches) {
435 return this._appletPanel.processTwoPointGesture(touches);
438 proto._processEvent = function(type, xym) {
439 this._appletPanel.processMouseEvent(type,xym[0],xym[1],xym[2],System.currentTimeMillis());
442 proto._resize = function() {
443 var s = "__resizeTimeout_" + this._id;
446 clearTimeout(Jmol[s]);
448 Jmol[s] = setTimeout(function() {Jmol._repaint(me, true);Jmol[s]=null}, 100);
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()
459 //alert("_repaint " + Clazz.getStackTrace())
460 if (!applet || !applet._appletPanel)return;
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);
470 applet._appletPanel.setScreenDimension(w, h);
472 if (applet._appletPanel.paint)
473 applet._appletPanel.paint(null);
475 applet._appletPanel.update(null)
482 // System.out.println(applet._appletPanel.getFullName())
485 Jmol._getHiddenCanvas = function(applet, id, width, height, forceNew) {
486 id = applet._id + "_" + id;
487 var d = document.getElementById(id);
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";
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
511 var path = echoNameAndPath[1];
514 var image = new Image();
515 image.onload = function() {Jmol._loadImage(platform, echoNameAndPath, null, fOnload, image)};
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" : "");
524 return true; // as far as we can tell!
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;
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);
539 Jmol._setCanvasImage = function(canvas, width, height) {
540 // called from org.jmol.awtjs2d.Platform
542 canvas.width = width;
543 canvas.height = height;
544 canvas.getContext("2d").drawImage(canvas.image, 0, 0, width, height);