1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2021-12-31 18:52:23 -0600 (Fri, 31 Dec 2021) $
4  * $Revision: 22287 $
5  *
6  * Copyright (C) 2003-2005  The Jmol Development Team
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 package org.jmol.viewer;
25 
26 import java.util.Hashtable;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 
30 import javajs.util.Lst;
31 import javajs.util.PT;
32 import javajs.util.T3;
33 
34 import org.jmol.api.GenericImageDialog;
35 import org.jmol.api.Interface;
36 import org.jmol.api.JmolAppConsoleInterface;
37 import org.jmol.api.JmolAudioPlayer;
38 import org.jmol.api.JmolCallbackListener;
39 import org.jmol.api.JmolDialogInterface;
40 import org.jmol.api.JmolStatusListener;
41 import org.jmol.c.CBK;
42 import javajs.util.BS;
43 import org.jmol.script.SV;
44 import org.jmol.script.T;
45 import org.jmol.util.JmolAudio;
46 import org.jmol.util.Logger;
47 
48 /**
49  *
50  * The StatusManager class handles all details of status reporting, including:
51  *
52  * 1) saving the message in a queue that replaces the "callback" mechanism,
53  * 2) sending messages off to the console, and
54  * 3) delivering messages back to the main Jmol.java class in app or applet
55  *    to handle differences in capabilities, including true callbacks.
56 
57 atomPicked
58 
59 fileLoaded
60 fileLoadError
61 frameChanged
62 
63 measureCompleted
64 measurePending
65 measurePicked
66 
67 newOrientation
68 
69 scriptEcho
70 scriptError
71 scriptMessage
72 scriptStarted
73 scriptStatus
74 scriptTerminated
75 
76 userAction
77 vwrRefreshed
78 
79 
80  * Bob Hanson hansonr@stolaf.edu  2/2006
81  *
82  */
83 
84 public class StatusManager {
85 
86   protected Viewer vwr;
87   JmolStatusListener jsl;
88   public JmolCallbackListener cbl;
89   public String statusList = "";
90 
StatusManager(Viewer vwr)91   StatusManager(Viewer vwr) {
92     this.vwr = vwr;
93   }
94 
95   public boolean allowStatusReporting; // set in StateManager.global
96 
97   /*
98    * the messageQueue provided a mechanism for saving and recalling
99    * information about the running of a script. The idea was to poll
100    * the applet instead of using callbacks.
101    *
102    * As it turns out, polling of applets is fraught with problems,
103    * not the least of which is the most odd behavior of some
104    * versions of Firefox that makes text entry into the URL line
105    * enter in reverse order of characters during applet polling.
106    * This bug may or may not have been resolved, but in any case,
107    * callbacks have proven far more efficient than polling anyway,
108    * so this mechanism is probably not particularly generally useful.
109    *
110    * Still, the mechanism is here because in addition to applet polling,
111    * it provides a way to get selective information back from the applet
112    * to the calling page after a script has run synchronously (using applet.scriptWait).
113    *
114    * The basic idea involves:
115    *
116    * 1) Setting what types of messages should be saved
117    * 2) Executing the scriptWait(script) call
118    * 3) Decoding the return value of that function
119    *
120    * Note that this is not meant to be a complete record of the script.
121    * Rather, each messsage type is saved in its own Vector, and
122    * only most recent MAX_QUEUE_LENGTH (16) entries are saved at any time.
123    *
124    * For example:
125    *
126    * 1) jmolGetStatus("scriptEcho,scriptMessage,scriptStatus,scriptError",targetSuffix)
127    *
128    * Here we flush the message queue and identify the status types we want to maintain.
129    *
130    * 2) var ret = "" + applet.scriptWait("background red;echo ok;echo hello;")
131    *
132    * The ret variable contains the array of messages in JSON format, which
133    * can then be reconstructed as a JavaScript array object. In this case the return is:
134    *
135    *
136    * {"jmolStatus": [
137    *  [
138    *    [ 3,"scriptEcho",0,"ok" ],
139    *    [ 4,"scriptEcho",0,"hello" ]
140    *  ],[
141    *    [ 1,"scriptStarted",6,"background red;echo ok;echo hello;" ]
142    *  ],[
143    *    [ 6,"scriptTerminated",1,"Jmol script terminated successfully" ]
144    *  ],[
145    *    [ 2,"scriptStatus",0,"script 6 started" ],
146    *    [ 5,"scriptStatus",0,"Script completed" ],
147    *    [ 7,"scriptStatus",0,"Jmol script terminated" ]
148    *  ]
149    *  ]}
150    *
151    * Decoded, what we have is a "jmolStatus" JavaScript Array. This array has 4 elements,
152    * our scriptEcho, scriptStarted, scriptTerminated, and scriptStatus messages.
153    *
154    * Within each of those elements we have the most recent 16 status messages.
155    * Each status record consists of four entries:
156    *
157    *   [ statusPtr, statusName, intInfo, strInfo ]
158    *
159    * The first entry in each record is the sequential number when that record
160    * was recorded, so to reconstruct the sequence of events, simply order the arrays:
161    *
162    *    [ 1,"scriptStarted",6,"background red;echo ok;echo hello;" ]
163    *    [ 2,"scriptStatus",0,"script 6 started" ],
164    *    [ 3,"scriptEcho",0,"ok" ],
165    *    [ 4,"scriptEcho",0,"hello" ]
166    *    [ 5,"scriptStatus",0,"Script completed" ],
167    *    [ 6,"scriptTerminated",1,"Jmol script terminated successfully" ]
168    *    [ 7,"scriptStatus",0,"Jmol script terminated" ]
169    *
170    * While it could be argued that the presence of the statusName in each record is
171    * redundant, and a better structure would be a Hashtable, this is the way it is
172    * implemented here and required for Jmol.js.
173    *
174    * Note that Jmol.js has a set of functions that manipulate this data.
175    *
176    */
177 
178   public Map<String, Lst<Lst<Object>>> messageQueue = new Hashtable<String, Lst<Lst<Object>>>();
179 
180   private int statusPtr = 0;
181   private static int MAXIMUM_QUEUE_LENGTH = 16;
182 
183 ////////////////////Jmol status //////////////
184 
recordStatus(String statusName)185   private boolean recordStatus(String statusName) {
186     return (allowStatusReporting && statusList.length() > 0
187         && (statusList.equals("all") || statusList.indexOf(statusName) >= 0));
188   }
189 
setStatusChanged(String statusName, int intInfo, Object statusInfo, boolean isReplace)190   synchronized private void setStatusChanged(String statusName,
191       int intInfo, Object statusInfo, boolean isReplace) {
192     if (!recordStatus(statusName))
193       return;
194     Lst<Object> msgRecord = new Lst<Object>();
195     msgRecord.addLast(Integer.valueOf(++statusPtr));
196     msgRecord.addLast(statusName);
197     msgRecord.addLast(Integer.valueOf(intInfo));
198     msgRecord.addLast(statusInfo);
199     Lst<Lst<Object>> statusRecordSet = (isReplace ? null : messageQueue.get(statusName));
200     if (statusRecordSet == null)
201       messageQueue.put(statusName, statusRecordSet = new  Lst<Lst<Object>>());
202     else if (statusRecordSet.size() == MAXIMUM_QUEUE_LENGTH)
203       statusRecordSet.removeItemAt(0);
204     statusRecordSet.addLast(msgRecord);
205   }
206 
getStatusChanged(String newStatusList)207   synchronized Lst<Lst<Lst<Object>>> getStatusChanged(String newStatusList) {
208     /*
209      * returns a Vector of statusRecordSets, one per status type,
210      * where each statusRecordSet is itself a vector of vectors:
211      * [int statusPtr,String statusName,int intInfo, String statusInfo]
212      *
213      * This allows selection of just the type desired as well as sorting
214      * by time overall.
215      *
216      */
217 
218     boolean isRemove = (newStatusList.length() > 0 && newStatusList.charAt(0) == '-');
219     boolean isAdd = (newStatusList.length() > 0 && newStatusList.charAt(0) == '+');
220     boolean getList = false;
221     if (isRemove) {
222       statusList = PT.rep(statusList,
223           newStatusList.substring(1, newStatusList.length()), "");
224     } else {
225       newStatusList = PT.rep(newStatusList, "+", "");
226       if (statusList.equals(newStatusList) || isAdd
227           && statusList.indexOf(newStatusList) >= 0) {
228         getList = true;
229       } else {
230         if (!isAdd)
231           statusList = "";
232         statusList += newStatusList;
233         if (Logger.debugging)
234           Logger.debug("StatusManager messageQueue = " + statusList);
235       }
236     }
237     Lst<Lst<Lst<Object>>> list = new Lst<Lst<Lst<Object>>>();
238     if (getList)
239       for (Map.Entry<String, Lst<Lst<Object>>> e : messageQueue.entrySet())
240         list.addLast(e.getValue());
241     messageQueue.clear();
242     statusPtr = 0;
243     return list;
244   }
245 
246   private Map<String, String> jmolScriptCallbacks = new Hashtable<String, String>();
247 
jmolScriptCallback(CBK callback)248   private String jmolScriptCallback(CBK callback) {
249     String s = jmolScriptCallbacks.get(callback.name());
250     if (s != null)
251       vwr.evalStringQuietSync(s, true, false);
252     if (jmolScriptCallbacks.containsKey("SYNC:" + callback.name()))
253       s = "SYNC";
254     return s;
255   }
256 
setCallbackFunction(String callbackType, String callbackFunction)257   synchronized void setCallbackFunction(String callbackType,
258                                         String callbackFunction) {
259     // menu and language setting also use this route
260     CBK cbk = CBK.getCallback(callbackType);
261     if (cbk != null) {
262       String callback = CBK.getCallback(callbackType).name();
263       Logger.info("StatusManager callback set for " + callbackType + " f="
264           + callbackFunction + " cb=" + callback);
265       boolean isSync = (callbackFunction != null
266           && callbackFunction.startsWith("SYNC:"));
267       if (isSync) {
268         if (callbackFunction.toLowerCase().trim().equals("sync:off")) {
269           jmolScriptCallbacks.remove("SYNC:" + callback);
270           Logger.info("SYNC callback for " + callback + " deactivated");
271         } else {
272           jmolScriptCallbacks.put("SYNC:" + callback, "_");
273           Logger.info("SYNC callback for " + callback + " activated");
274           return;
275         }
276       } else {
277         String lc = "";
278         int pt = (callbackFunction == null ? 0
279             : (lc = callbackFunction.toLowerCase()).startsWith("script:") ? 7
280                 : lc.startsWith("jmolscript:") ? 11 : 0);
281         if (pt == 0) {
282           jmolScriptCallbacks.remove(callback);
283         } else {
284           jmolScriptCallbacks.put(callback,
285               callbackFunction.substring(pt).trim());
286         }
287       }
288     }
289     // allow for specialized callbacks
290     if (cbl != null)
291       cbl.setCallbackFunction(callbackType, callbackFunction);
292   }
293 
notifyEnabled(CBK type)294   boolean notifyEnabled(CBK type) {
295     return cbl != null && cbl.notifyEnabled(type);
296   }
297 
setStatusAppletReady(String htmlName, boolean isReady)298   synchronized void setStatusAppletReady(String htmlName, boolean isReady) {
299     String sJmol = (isReady ? jmolScriptCallback(CBK.APPLETREADY) : null);
300     if (notifyEnabled(CBK.APPLETREADY))
301       cbl.notifyCallback(CBK.APPLETREADY,
302           new Object[] { sJmol, htmlName, Boolean.valueOf(isReady), null });
303   }
304 
setStatusAtomMoved(BS bsMoved)305   synchronized void setStatusAtomMoved(BS bsMoved) {
306     String sJmol = jmolScriptCallback(CBK.ATOMMOVED);
307     setStatusChanged("atomMoved", -1, bsMoved, false);
308     if (notifyEnabled(CBK.ATOMMOVED))
309       cbl.notifyCallback(CBK.ATOMMOVED,
310           new Object[] { sJmol, bsMoved });
311   }
312 
313   /**
314    *
315    * @param atomIndex  -2 for draw, -3 for bond
316    * @param strInfo
317    * @param map
318    */
setStatusAtomPicked(int atomIndex, String strInfo, Map<String, Object> map)319   synchronized void setStatusAtomPicked(int atomIndex, String strInfo, Map<String, Object> map) {
320     String sJmol = jmolScriptCallback(CBK.PICK);
321     Logger.info("setStatusAtomPicked(" + atomIndex + "," + strInfo + ")");
322     setStatusChanged("atomPicked", atomIndex, strInfo, false);
323     if (notifyEnabled(CBK.PICK))
324       cbl.notifyCallback(CBK.PICK,
325           new Object[] { sJmol, strInfo, Integer.valueOf(atomIndex), map });
326   }
327 
setStatusClicked(int x, int y, int action, int clickCount, int mode)328   synchronized int setStatusClicked(int x, int y, int action, int clickCount, int mode) {
329     // also called on drag movements
330     String sJmol = jmolScriptCallback(CBK.CLICK);
331     if (!notifyEnabled(CBK.CLICK))
332       return action;
333     // allows modification of action
334     int[] m = new int[] { action, mode };
335     cbl.notifyCallback(CBK.CLICK,
336         new Object[] { sJmol, Integer.valueOf(x), Integer.valueOf(y), Integer.valueOf(action), Integer.valueOf(clickCount), m });
337     return m[0];
338   }
339 
setStatusResized(int width, int height)340   synchronized void setStatusResized(int width, int height){
341     String sJmol = jmolScriptCallback(CBK.RESIZE);
342     if (notifyEnabled(CBK.RESIZE))
343       cbl.notifyCallback(CBK.RESIZE,
344           new Object[] { sJmol, Integer.valueOf(width), Integer.valueOf(height) });
345   }
346 
haveHoverCallback()347   boolean haveHoverCallback() {
348     return (jmolScriptCallbacks.containsKey(CBK.HOVER) || notifyEnabled(CBK.HOVER));
349   }
350 
setStatusAtomHovered(int iatom, String strInfo)351   synchronized void setStatusAtomHovered(int iatom, String strInfo) {
352     String sJmol = jmolScriptCallback(CBK.HOVER);
353     if (notifyEnabled(CBK.HOVER))
354       cbl.notifyCallback(CBK.HOVER,
355           new Object[] {sJmol, strInfo, Integer.valueOf(iatom) });
356   }
357 
setStatusObjectHovered(String id, String strInfo, T3 pt)358   synchronized void setStatusObjectHovered(String id, String strInfo, T3 pt) {
359     String sJmol = jmolScriptCallback(CBK.HOVER);
360     if (notifyEnabled(CBK.HOVER))
361       cbl.notifyCallback(CBK.HOVER,
362           new Object[] {sJmol, strInfo, Integer.valueOf(-1), id, Float.valueOf(pt.x), Float.valueOf(pt.y), Float.valueOf(pt.z) });
363   }
364 
365   private Map<String, GenericImageDialog> imageMap;
366 
367   /**
368    * called by Viewer.loadImageData to pop up a window with an image in it
369    *
370    * @param title
371    * @param image  or Boolean.TRUE for "close all" or Boolean.FALSE for "close"
372    */
showImage(String title, Object image)373   synchronized void showImage(String title, Object image) {
374     String[] a = PT.split(title,  "\1");
375     title = (a.length < 2 ? "Jmol" : a.length < 3 || a[2].equals("null") ? a[1].substring(a[1].lastIndexOf("/") + 1) : a[2]);
376     String sJmol = jmolScriptCallback(CBK.IMAGE);
377     if (notifyEnabled(CBK.IMAGE))
378       cbl.notifyCallback(CBK.IMAGE, new Object[] { sJmol, title, image });
379     if (Boolean.TRUE.equals(image)) {
380       if (imageMap == null)
381         return;
382       Lst<String> lst = new Lst<String>();
383       for (String key : imageMap.keySet())
384         lst.addLast(key);
385       for (int i = lst.size(); --i >= 0;)
386         imageMap.get(lst.get(i)).closeMe();
387       return;
388     }
389     if (imageMap == null)
390       imageMap = new Hashtable<String, GenericImageDialog>();
391     GenericImageDialog d = imageMap.get(title);
392     if (Boolean.FALSE.equals(image)) {
393       if (d != null)
394         d.closeMe();
395       return;
396     }
397     if (d == null && image != null)
398       d = vwr.apiPlatform.getImageDialog(title, imageMap);
399     if (d == null)
400       return;
401     if (image == null)
402       d.closeMe();
403     else
404       d.setImage(image);
405   }
406 
setFileLoadStatus(String fullPathName, String fileName, String modelName, String errorMsg, int ptLoad, boolean doCallback, Boolean isAsync)407   synchronized void setFileLoadStatus(String fullPathName, String fileName,
408                                       String modelName, String errorMsg,
409                                       int ptLoad, boolean doCallback,
410                                       Boolean isAsync) {
411     if (fullPathName == null && "resetUndo".equals(fileName)) {
412       JmolAppConsoleInterface appConsole = (JmolAppConsoleInterface) vwr
413           .getProperty("DATA_API", "getAppConsole", null);
414       if (appConsole != null)
415         appConsole.zap();
416       fileName = vwr.getZapName();
417     }
418     setStatusChanged("fileLoaded", ptLoad, fullPathName, false);
419     if (errorMsg != null)
420       setStatusChanged("fileLoadError", ptLoad, errorMsg, false);
421     String sJmol = jmolScriptCallback(CBK.LOADSTRUCT);
422     if (doCallback && notifyEnabled(CBK.LOADSTRUCT)) {
423       String name = (String) vwr.getP("_smilesString");
424       if (name.length() != 0)
425         fileName = name;
426       cbl
427           .notifyCallback(CBK.LOADSTRUCT,
428               new Object[] { sJmol, fullPathName, fileName, modelName,
429                   errorMsg, Integer.valueOf(ptLoad),
430                   vwr.getP("_modelNumber"),
431                   vwr.getModelNumberDotted(vwr.ms.mc - 1),
432                   isAsync });
433     }
434   }
435 
setStatusModelKit(int istate)436   synchronized void setStatusModelKit(int istate) {
437     String state = (istate == 1 ? "ON" : "OFF");
438     setStatusChanged("modelkit", istate, state, false);
439     String sJmol = jmolScriptCallback(CBK.MODELKIT);
440     if (notifyEnabled(CBK.MODELKIT))
441       cbl.notifyCallback(CBK.MODELKIT,
442           new Object[] { sJmol, state });
443   }
444 
445 
setStatusFrameChanged(int fileNo, int modelNo, int firstNo, int lastNo, int currentFrame, float currentMorphModel, String entryName)446   synchronized void setStatusFrameChanged(int fileNo, int modelNo, int firstNo,
447                                           int lastNo, int currentFrame,
448                                           float currentMorphModel, String entryName) {
449     if (vwr.ms == null)
450       return;
451     boolean animating = vwr.am.animationOn;
452     int frameNo = (animating ? -2 - currentFrame : currentFrame);
453     setStatusChanged("frameChanged", frameNo,
454         (currentFrame >= 0 ? vwr.getModelNumberDotted(currentFrame) : ""), false);
455     String sJmol = jmolScriptCallback(CBK.ANIMFRAME);
456     if (notifyEnabled(CBK.ANIMFRAME))
457       cbl.notifyCallback(CBK.ANIMFRAME,
458           new Object[] {
459               sJmol,
460               new int[] { frameNo, fileNo, modelNo, firstNo, lastNo,
461                   currentFrame }, entryName, Float.valueOf(currentMorphModel) });
462     if (!animating && !vwr.isJSNoAWT)
463       vwr.checkMenuUpdate();
464   }
465 
setStatusDragDropped(int mode, int x, int y, String fileName)466   synchronized boolean setStatusDragDropped(int mode, int x, int y,
467                                             String fileName) {
468     setStatusChanged("dragDrop", 0, "", false);
469     String sJmol = jmolScriptCallback(CBK.DRAGDROP);
470     if (!notifyEnabled(CBK.DRAGDROP))
471       return false;
472     cbl.notifyCallback(CBK.DRAGDROP,
473         new Object[] { sJmol, Integer.valueOf(mode), Integer.valueOf(x),
474             Integer.valueOf(y), fileName });
475     return true;
476   }
477 
setScriptEcho(String strEcho, boolean isScriptQueued)478   synchronized void setScriptEcho(String strEcho,
479                                   boolean isScriptQueued) {
480     if (strEcho == null)
481       return;
482     setStatusChanged("scriptEcho", 0, strEcho, false);
483     String sJmol = jmolScriptCallback(CBK.ECHO);
484     if (notifyEnabled(CBK.ECHO))
485       cbl.notifyCallback(CBK.ECHO,
486           new Object[] { sJmol, strEcho, Integer.valueOf(isScriptQueued ? 1 : 0) });
487   }
488 
setStatusMeasuring(String status, int intInfo, String strMeasure, float value)489   synchronized void setStatusMeasuring(String status, int intInfo, String strMeasure, float value) {
490     setStatusChanged(status, intInfo, strMeasure, false);
491     String sJmol = null;
492     if(status.equals("measureCompleted")) {
493       Logger.info("measurement["+intInfo+"] = "+strMeasure);
494       sJmol = jmolScriptCallback(CBK.MEASURE);
495     } else if (status.equals("measurePicked")) {
496         setStatusChanged("measurePicked", intInfo, strMeasure, false);
497         Logger.info("measurePicked " + intInfo + " " + strMeasure);
498     }
499     if (notifyEnabled(CBK.MEASURE))
500       cbl.notifyCallback(CBK.MEASURE,
501           new Object[] { sJmol, strMeasure,  Integer.valueOf(intInfo), status , Float.valueOf(value)});
502   }
503 
notifyError(String errType, String errMsg, String errMsgUntranslated)504   synchronized void notifyError(String errType, String errMsg,
505                                 String errMsgUntranslated) {
506     String sJmol = jmolScriptCallback(CBK.ERROR);
507     if (notifyEnabled(CBK.ERROR))
508       cbl.notifyCallback(CBK.ERROR,
509           new Object[] { sJmol, errType, errMsg, vwr.getShapeErrorState(),
510               errMsgUntranslated });
511   }
512 
513 
notifyMinimizationStatus(String minStatus, Integer minSteps, Float minEnergy, Float minEnergyDiff, String ff)514   synchronized void notifyMinimizationStatus(String minStatus, Integer minSteps,
515                                              Float minEnergy, Float minEnergyDiff, String ff) {
516     String sJmol = jmolScriptCallback(CBK.MINIMIZATION);
517     if (notifyEnabled(CBK.MINIMIZATION))
518       cbl.notifyCallback(CBK.MINIMIZATION,
519           new Object[] { sJmol, minStatus, minSteps, minEnergy, minEnergyDiff, ff });
520   }
521 
setScriptStatus(String strStatus, String statusMessage, int msWalltime, String strErrorMessageUntranslated)522   synchronized void setScriptStatus(String strStatus, String statusMessage,
523                                     int msWalltime,
524                                     String strErrorMessageUntranslated) {
525     // only allow trapping of script information of type 0
526     if (msWalltime < -1) {
527       int iscript = -2 - msWalltime;
528       setStatusChanged("scriptStarted", iscript, statusMessage, false);
529       strStatus = "script " + iscript + " started";
530     } else if (strStatus == null) {
531       return;
532     }
533     String sJmol = (msWalltime == 0 ? jmolScriptCallback(CBK.SCRIPT)
534         : null);
535     boolean isScriptCompletion = (strStatus == JC.SCRIPT_COMPLETED);
536 
537     if (recordStatus("script")) {
538       boolean isError = (strErrorMessageUntranslated != null);
539       setStatusChanged((isError ? "scriptError" : "scriptStatus"), 0,
540           strStatus, false);
541       if (isError || isScriptCompletion)
542         setStatusChanged("scriptTerminated", 1, "Jmol script terminated"
543             + (isError ? " unsuccessfully: " + strStatus : " successfully"),
544             false);
545     }
546 
547     if (isScriptCompletion && vwr.getBoolean(T.messagestylechime)
548         && vwr.getBoolean(T.debugscript))
549       strStatus = vwr.getChimeMessenger().scriptCompleted(this, statusMessage, strErrorMessageUntranslated);
550     Object[] data = new Object[] { sJmol, strStatus, statusMessage,
551         Integer.valueOf(isScriptCompletion ? -1 : msWalltime),
552         strErrorMessageUntranslated };
553     if (notifyEnabled(CBK.SCRIPT))
554       cbl.notifyCallback(CBK.SCRIPT, data);
555     processScript(data);
556   }
557 
processScript(Object[] data)558   void processScript(Object[] data) {
559     int msWalltime = ((Integer) data[3]).intValue();
560     // general message has msWalltime = 0
561     // special messages have msWalltime < 0
562     // termination message has msWalltime > 0 (1 + msWalltime)
563     // "script started"/"pending"/"script terminated"/"script completed"
564     // do not get sent to console
565 
566     vwr.notifyScriptEditor(msWalltime, data);
567     if (msWalltime == 0)
568       vwr.sendConsoleMessage(data[1] == null ? null : data[1].toString());
569   }
570 
571   private int minSyncRepeatMs = 100;
572   public boolean syncingScripts = false;
573   boolean syncingMouse = false;
doSync()574   boolean doSync() {
575     return (isSynced && drivingSync && !syncDisabled);
576   }
577 
setSync(String mouseCommand)578   synchronized void setSync(String mouseCommand) {
579     if (syncingMouse) {
580       if (mouseCommand != null)
581         syncSend(mouseCommand, "*", 0);
582     } else if (!syncingScripts)
583       syncSend("!" + vwr.tm.getMoveToText(minSyncRepeatMs / 1000f, false), "*", 0);
584   }
585 
586   private boolean drivingSync;
587   private boolean isSynced;
588   private boolean syncDisabled;
589   boolean stereoSync;
590 
591   public final static int SYNC_OFF = 0;
592   public final static int SYNC_DRIVER = 1;
593   public final static int SYNC_SLAVE = 2;
594   public final static int SYNC_DISABLE = 3;
595   public final static int SYNC_ENABLE = 4;
596   public final static int SYNC_STEREO = 5;
597 
setSyncDriver(int syncMode)598   void setSyncDriver(int syncMode) {
599 
600     // -1 slave   turn off driving, but not syncing
601     //  0 off
602     //  1 driving on as driver
603     //  2 sync    turn on, but set as slave
604     //  5 stereo
605     if (stereoSync && syncMode != SYNC_ENABLE) {
606       syncSend(Viewer.SYNC_NO_GRAPHICS_MESSAGE, "*", 0);
607       stereoSync = false;
608     }
609     switch (syncMode) {
610     case SYNC_ENABLE:
611       if (!syncDisabled)
612         return;
613       syncDisabled = false;
614       break;
615     case SYNC_DISABLE:
616       syncDisabled = true;
617       break;
618     case SYNC_STEREO:
619       drivingSync = true;
620       isSynced = true;
621       stereoSync = true;
622       break;
623     case SYNC_DRIVER:
624       drivingSync = true;
625       isSynced = true;
626       break;
627     case SYNC_SLAVE:
628       drivingSync = false;
629       isSynced = true;
630       break;
631     default:
632       drivingSync = false;
633       isSynced = false;
634     }
635     if (Logger.debugging) {
636       Logger.debug(
637           vwr.appletName + " sync mode=" + syncMode +
638           "; synced? " + isSynced + "; driving? " + drivingSync + "; disabled? " + syncDisabled);
639     }
640   }
641 
syncSend(String script, Object appletNameOrProp, int port)642   public Object syncSend(String script, Object appletNameOrProp, int port) {
643     // no jmolscript option for syncSend
644     if (port != 0 || notifyEnabled(CBK.SYNC)) {
645       Object[] o = new Object[] { null, script, appletNameOrProp, Integer.valueOf(port) };
646       if (cbl != null)
647         cbl.notifyCallback(CBK.SYNC, o);
648       return o[0];
649     }
650     return null;
651   }
652 
modifySend(int atomIndex, int modelIndex, int mode, String msg)653   public void modifySend(int atomIndex, int modelIndex, int mode, String msg) {
654     String sJmol = jmolScriptCallback(CBK.STRUCTUREMODIFIED);
655     if (notifyEnabled(CBK.STRUCTUREMODIFIED))
656       cbl.notifyCallback(CBK.STRUCTUREMODIFIED,
657           new Object[] { sJmol, Integer.valueOf(mode), Integer.valueOf(atomIndex), Integer.valueOf(modelIndex), msg });
658   }
659 
660   /**
661    * service is expected to return a value in the "ret" key
662    *
663    * @param info
664    *        with key "service"
665    * @return info, for chaining
666    *
667    */
processService(Map<String, Object> info)668   public Object processService(Map<String, Object> info) {
669     Object s = info.get("service");
670     if (s == null)
671       return null;
672     if (s instanceof SV) {
673       Map<String, Object> m = new Hashtable<String, Object>();
674       for (Entry<String, Object> e : info.entrySet())
675         m.put(e.getKey(), SV.oValue(e.getValue()));
676       info = m;
677     }
678     if (notifyEnabled(CBK.SERVICE))
679       cbl.notifyCallback(CBK.SERVICE, new Object[] { null, info });
680     return info;
681   }
682 
getSyncMode()683   public int getSyncMode() {
684     return (!isSynced ? SYNC_OFF : drivingSync ? SYNC_DRIVER : SYNC_SLAVE);
685   }
686 
showUrl(String urlString)687   synchronized void showUrl(String urlString) {
688     if (jsl != null)
689       jsl.showUrl(urlString);
690   }
691 
clearConsole()692   public synchronized void clearConsole() {
693     vwr.sendConsoleMessage(null);
694     if (jsl != null)
695       cbl.notifyCallback(CBK.MESSAGE, null);
696   }
697 
functionXY(String functionName, int nX, int nY)698   float[][] functionXY(String functionName, int nX, int nY) {
699     return (jsl == null ? new float[Math.abs(nX)][Math.abs(nY)] :
700       jsl.functionXY(functionName, nX, nY));
701   }
702 
functionXYZ(String functionName, int nX, int nY, int nZ)703   float[][][] functionXYZ(String functionName, int nX, int nY, int nZ) {
704     return (jsl == null ? new float[Math.abs(nX)][Math.abs(nY)][Math.abs(nY)] :
705       jsl.functionXYZ(functionName, nX, nY, nZ));
706   }
707 
708   /**
709    *
710    * @param strEval
711    * @return in Java a String; in JavaScript window.eval()
712    *
713    */
jsEval(String strEval)714   String jsEval(String strEval) {
715     return (jsl == null ? "" : jsl.eval(strEval));
716   }
717 
718   /**
719    * offer to let application do the image creation.
720    * if text_or_bytes == null, then this is an error report.
721    *
722    * @param fileNameOrError
723    * @param type
724    * @param text
725    * @param bytes
726    * @param quality
727    * @return null (canceled) or a message starting with OK or an error message
728    */
createImage(String fileNameOrError, String type, String text, byte[] bytes, int quality)729   String createImage(String fileNameOrError, String type, String text, byte[] bytes,
730                      int quality) {
731     return (jsl == null  ? null :
732       jsl.createImage(fileNameOrError, type, text == null ? bytes : text, quality));
733   }
734 
getRegistryInfo()735   Map<String, Object> getRegistryInfo() {
736     /*
737 
738      //note that the following JavaScript retrieves the registry:
739 
740         var registry = jmolGetPropertyAsJavaObject("appletInfo").get("registry")
741 
742      // and the following code then retrieves an array of applets:
743 
744         var AppletNames = registry.keySet().toArray()
745 
746      // and the following sends commands to an applet in the registry:
747 
748         registry.get(AppletNames[0]).script("background white")
749 
750      */
751     return (jsl == null ? null : jsl.getRegistryInfo());
752   }
753 
754   private int qualityJPG = -1;
755   private int qualityPNG = -1;
756   private String imageType;
757 
dialogAsk(String type, String fileName, Map<String, Object> params)758   String dialogAsk(String type, String fileName, Map<String, Object> params) {
759     boolean isImage = type.equals("Save Image");
760     JmolDialogInterface sd = (JmolDialogInterface) Interface.getOption(
761         "dialog.Dialog", vwr, "status");
762     if (sd == null)
763       return null;
764     sd.setupUI(false);
765     if (isImage)
766       sd.setImageInfo(qualityJPG, qualityPNG, imageType);
767     String outputFileName = sd.getFileNameFromDialog(vwr, type, fileName); // may have #NOCARTOONS#; and/or "#APPEND#; prepended
768     // may have #NOCARTOONS#; and/or "#APPEND#; prepended
769 
770     if (isImage && outputFileName != null) {
771       qualityJPG = sd.getQuality("JPG");
772       qualityPNG = sd.getQuality("PNG");
773       String sType = sd.getType();
774       if (params != null) {
775         params.put("qualityJPG", Integer.valueOf(qualityJPG));
776         params.put("qualityPNG", Integer.valueOf(qualityPNG));
777         if (sType != null)
778           params.put("dialogImageType", sType);
779       }
780       if (sType != null)
781         imageType = sType;
782     }
783     return outputFileName;
784   }
785 
getJspecViewProperties(String myParam)786   Map<String, Object> getJspecViewProperties(String myParam) {
787     return (jsl == null ? null : jsl
788         .getJSpecViewProperty(myParam == null || myParam.length() == 0 ? "" : ":" + myParam));
789   }
790 
resizeInnerPanel(int width, int height)791   public int[] resizeInnerPanel(int width, int height) {
792     return (jsl == null || width == vwr.getScreenWidth()
793         && height == vwr.getScreenHeight() ? new int[] { width, height } : jsl
794         .resizeInnerPanel("preferredWidthHeight " + width + " " + height + ";"));
795   }
796 
797   /**
798    * called by file droppers
799    *
800    * @param data
801    */
resizeInnerPanelString(String data)802   public void resizeInnerPanelString(String data) {
803     if (jsl != null)
804       jsl.resizeInnerPanel(data);
805   }
806 
807   private Map<String, JmolAudioPlayer>audios;
808 
registerAudio(String id, Map<String, Object> htParams)809   public void registerAudio(String id, Map<String, Object> htParams) {
810     stopAudio(id);
811     if (audios == null)
812       audios = new Hashtable<String, JmolAudioPlayer>();
813     if (htParams == null)
814       audios.remove(id);
815     else
816       audios.put(id, (JmolAudioPlayer) htParams.get("audioPlayer"));
817   }
818 
stopAudio(String id)819   private void stopAudio(String id) {
820     if (audios == null)
821       return;
822     JmolAudioPlayer player = audios.get(id);
823     if (player != null)
824       player.action("kill");
825   }
826 
playAudio(Map<String, Object> htParams)827   public void playAudio(Map<String, Object> htParams) {
828     if (!vwr.getBoolean(T.allowaudio)) {
829       if (htParams == null)
830         return;
831       htParams.put("status", "close");
832       Logger.info("allowAudio is set false");
833       notifyAudioStatus(htParams);
834       return;
835     }
836     try {
837       String action = (htParams == null ? "close" : (String) htParams
838           .get("action"));
839       String id = (htParams == null ? null : (String) htParams.get("id"));
840       if (action != null && action.length() > 0) {
841         if (id == null || id.length() == 0) {
842           if (audios == null || audios.isEmpty())
843             return;
844           if (action.equals("close")) {
845             for (String key : audios.keySet()) {
846               JmolAudioPlayer player = audios.remove(key);
847               player.action("close");
848             }
849           }
850           return;
851         }
852         JmolAudioPlayer player = audios.get(id);
853         if (player != null) {
854           player.action(action);
855           return;
856         }
857       }
858       try {
859         ((JmolAudio) Interface.getInterface("org.jmol.util.JmolAudio", vwr,
860             "script")).playAudio(vwr, htParams);
861       } catch (Exception e) {
862         Logger.info(e.getMessage());
863       }
864     } catch (Throwable t) {
865       // ignore
866     }
867   }
868 
869   /**
870    * called from JmolAudio
871    *
872    * @param htParams
873    */
notifyAudioStatus(Map<String, Object> htParams)874   synchronized public void notifyAudioStatus(Map<String, Object> htParams) {
875     String status = (String) htParams.get("status");
876     System.out.println(status);
877     String script = (String) htParams.get(status);
878     if (script != null)
879       vwr.script(script);
880     if (status == "ended")
881       registerAudio((String) htParams.get("id"), null);
882     String sJmol = jmolScriptCallback(CBK.AUDIO);
883     if (notifyEnabled(CBK.AUDIO))
884       cbl.notifyCallback(CBK.AUDIO,
885           new Object[] { sJmol, htParams });
886   }
887 
888 
syncScript(String script, String applet, int port)889  void syncScript(String script, String applet, int port) {
890     if (Viewer.SYNC_GRAPHICS_MESSAGE.equalsIgnoreCase(script)) {
891       setSyncDriver(StatusManager.SYNC_STEREO);
892       syncSend(script, applet, 0);
893       vwr.setBooleanProperty("_syncMouse", false);
894       vwr.setBooleanProperty("_syncScript", false);
895       return;
896     }
897     // * : all applets
898     // > : all OTHER applets
899     // . : just me
900     // ~ : disable send (just me)
901     // = : disable send (just me) and force slave
902     if ("=".equals(applet)) {
903       applet = "~";
904       setSyncDriver(StatusManager.SYNC_SLAVE);
905     }
906     boolean disableSend = "~".equals(applet);
907     // null same as ">" -- "all others"
908     if (port > 0 || !disableSend && !".".equals(applet)) {
909       syncSend(script, applet, port);
910       if (!"*".equals(applet) || script.startsWith("{"))
911         return;
912     }
913     if (script.equalsIgnoreCase("on") || script.equalsIgnoreCase("true")) {
914       setSyncDriver(StatusManager.SYNC_DRIVER);
915       return;
916     }
917     if (script.equalsIgnoreCase("off") || script.equalsIgnoreCase("false")) {
918       setSyncDriver(StatusManager.SYNC_OFF);
919       return;
920     }
921     if (script.equalsIgnoreCase("slave")) {
922       setSyncDriver(StatusManager.SYNC_SLAVE);
923       return;
924     }
925     int syncMode = getSyncMode();
926     if (syncMode == StatusManager.SYNC_OFF)
927       return;
928     if (syncMode != StatusManager.SYNC_DRIVER)
929       disableSend = false;
930     if (Logger.debugging)
931       Logger.debug(vwr.htmlName + " syncing with script: " + script);
932     // driver is being positioned by another driver -- don't pass on the change
933     // driver is being positioned by a mouse movement
934     // format is from above refresh(Viewer.REFRESH_SYNC, xxx) calls
935     // Mouse: [CommandName] [value1] [value2]
936     if (disableSend)
937       setSyncDriver(StatusManager.SYNC_DISABLE);
938     if (script.indexOf("Mouse: ") != 0) {
939       int serviceMode = JC.getServiceCommand(script);
940       switch (serviceMode) {
941       case JC.NBO_CONFIG:
942       case JC.NBO_MODEL:
943       case JC.NBO_RUN:
944       case JC.NBO_VIEW:
945       case JC.NBO_SEARCH:
946         syncSend(script, ".", port);
947         return;
948       case JC.JSV_NOT:
949         break;
950       case JC.JSV_SEND_JDXMOL:
951       case JC.JSV_CLOSE:
952       case JC.JSV_SEND_H1SIMULATE:
953       case JC.JSV_SEND_C13SIMULATE:
954         if (disableSend)
955           return;
956         //$FALL-THROUGH$
957       case JC.JSV_STRUCTURE:
958       case JC.JSV_SETPEAKS:
959       case JC.JSV_SELECT:
960         // from JSpecView...
961         if ((script = vwr.getJSV().processSync(script, serviceMode)) == null)
962           return;
963       }
964       //System.out.println("Jmol executing script for JSpecView: " + script);
965       vwr.evalStringQuietSync(script, true, false);
966       return;
967     }
968     mouseScript(script);
969     if (disableSend)
970       vwr.setSyncDriver(StatusManager.SYNC_ENABLE);
971   }
972 
mouseScript(String script)973   void mouseScript(String script) {
974     String[] tokens = PT.getTokens(script);
975     String key = tokens[1];
976     try {
977       key = (key.toLowerCase() + "...............").substring(0, 15);
978       switch ((
979           "zoombyfactor..." +
980           "zoomby........." +
981           "rotatezby......" +
982           "rotatexyby....." +
983           "translatexyby.." +
984           "rotatemolecule." +
985           "spinxyby......." +
986           "rotatearcball..").indexOf(key)) {
987       case 0: //zoombyfactor
988         switch (tokens.length) {
989         case 3:
990           vwr.zoomByFactor(PT.parseFloat(tokens[2]),
991               Integer.MAX_VALUE, Integer.MAX_VALUE);
992           return;
993         case 5:
994           vwr.zoomByFactor(PT.parseFloat(tokens[2]), javajs.util.PT
995               .parseInt(tokens[3]), PT.parseInt(tokens[4]));
996           return;
997         }
998         break;
999       case 15: //zoomby
1000         switch (tokens.length) {
1001         case 3:
1002           vwr.zoomBy(PT.parseInt(tokens[2]));
1003           return;
1004         }
1005         break;
1006       case 30: // rotatezby
1007         switch (tokens.length) {
1008         case 3:
1009           vwr.rotateZBy(PT.parseInt(tokens[2]), Integer.MAX_VALUE,
1010               Integer.MAX_VALUE);
1011           return;
1012         case 5:
1013           vwr.rotateZBy(PT.parseInt(tokens[2]), javajs.util.PT
1014               .parseInt(tokens[3]), PT.parseInt(tokens[4]));
1015         }
1016         break;
1017       case 45: // rotatexyby
1018         vwr.rotateXYBy(PT.parseFloat(tokens[2]), PT
1019             .parseFloat(tokens[3]));
1020         return;
1021       case 60: // translatexyby
1022         vwr.translateXYBy(PT.parseInt(tokens[2]), javajs.util.PT
1023             .parseInt(tokens[3]));
1024         return;
1025       case 75: // rotatemolecule
1026         vwr.rotateSelected(PT.parseFloat(tokens[2]), PT
1027             .parseFloat(tokens[3]), null);
1028         return;
1029       case 90:// spinxyby
1030         vwr.spinXYBy(PT.parseInt(tokens[2]), PT.parseInt(tokens[3]),
1031             PT.parseFloat(tokens[4]));
1032         return;
1033       case 105: // rotatearcball
1034         vwr.rotateXYBy(PT.parseInt(tokens[2]), javajs.util.PT
1035             .parseInt(tokens[3]));//, PT.parseFloat(tokens[4]));
1036         return;
1037       }
1038     } catch (Exception e) {
1039       //
1040     }
1041     vwr.showString("error reading SYNC command: " + script, false);
1042   }
1043 
1044 
1045 }
1046 
1047