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