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