1 /* 2 * Jalview - A Sequence Alignment Editor and Viewer (2.11.1.4) 3 * Copyright (C) 2021 The Jalview Authors 4 * 5 * This file is part of Jalview. 6 * 7 * Jalview is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation, either version 3 10 * of the License, or (at your option) any later version. 11 * 12 * Jalview is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 15 * PURPOSE. See the GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>. 19 * The Jalview Authors are detailed in the 'AUTHORS' file. 20 */ 21 package jalview.ext.jmol; 22 23 import java.awt.Color; 24 import java.awt.Container; 25 import java.awt.event.ComponentEvent; 26 import java.awt.event.ComponentListener; 27 import java.io.File; 28 import java.net.URL; 29 import java.util.ArrayList; 30 import java.util.BitSet; 31 import java.util.Hashtable; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.StringTokenizer; 35 import java.util.Vector; 36 37 import javax.swing.SwingUtilities; 38 39 import org.jmol.adapter.smarter.SmarterJmolAdapter; 40 import org.jmol.api.JmolAppConsoleInterface; 41 import org.jmol.api.JmolSelectionListener; 42 import org.jmol.api.JmolStatusListener; 43 import org.jmol.api.JmolViewer; 44 import org.jmol.c.CBK; 45 import org.jmol.script.T; 46 import org.jmol.viewer.Viewer; 47 48 import jalview.api.AlignmentViewPanel; 49 import jalview.api.FeatureRenderer; 50 import jalview.api.FeatureSettingsModelI; 51 import jalview.api.SequenceRenderer; 52 import jalview.datamodel.AlignmentI; 53 import jalview.datamodel.HiddenColumns; 54 import jalview.datamodel.PDBEntry; 55 import jalview.datamodel.SequenceI; 56 import jalview.gui.AppJmol; 57 import jalview.gui.IProgressIndicator; 58 import jalview.io.DataSourceType; 59 import jalview.io.StructureFile; 60 import jalview.schemes.ColourSchemeI; 61 import jalview.schemes.ResidueProperties; 62 import jalview.structure.AtomSpec; 63 import jalview.structure.StructureMappingcommandSet; 64 import jalview.structure.StructureSelectionManager; 65 import jalview.structures.models.AAStructureBindingModel; 66 import jalview.util.MessageManager; 67 import jalview.ws.dbsources.Pdb; 68 69 public abstract class JalviewJmolBinding extends AAStructureBindingModel 70 implements JmolStatusListener, JmolSelectionListener, 71 ComponentListener 72 { 73 private String lastMessage; 74 75 boolean allChainsSelected = false; 76 77 /* 78 * when true, try to search the associated datamodel for sequences that are 79 * associated with any unknown structures in the Jmol view. 80 */ 81 private boolean associateNewStructs = false; 82 83 Vector<String> atomsPicked = new Vector<>(); 84 85 private List<String> chainNames; 86 87 Hashtable<String, String> chainFile; 88 89 /* 90 * the default or current model displayed if the model cannot be identified 91 * from the selection message 92 */ 93 int frameNo = 0; 94 95 // protected JmolGenericPopup jmolpopup; // not used - remove? 96 97 String lastCommand; 98 99 boolean loadedInline; 100 101 StringBuffer resetLastRes = new StringBuffer(); 102 103 public Viewer viewer; 104 JalviewJmolBinding(StructureSelectionManager ssm, PDBEntry[] pdbentry, SequenceI[][] sequenceIs, DataSourceType protocol)105 public JalviewJmolBinding(StructureSelectionManager ssm, 106 PDBEntry[] pdbentry, SequenceI[][] sequenceIs, 107 DataSourceType protocol) 108 { 109 super(ssm, pdbentry, sequenceIs, protocol); 110 /* 111 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(), 112 * "jalviewJmol", ap.av.applet .getDocumentBase(), 113 * ap.av.applet.getCodeBase(), "", this); 114 * 115 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true); 116 */ 117 } 118 JalviewJmolBinding(StructureSelectionManager ssm, SequenceI[][] seqs, Viewer theViewer)119 public JalviewJmolBinding(StructureSelectionManager ssm, 120 SequenceI[][] seqs, Viewer theViewer) 121 { 122 super(ssm, seqs); 123 124 viewer = theViewer; 125 viewer.setJmolStatusListener(this); 126 viewer.addSelectionListener(this); 127 } 128 129 /** 130 * construct a title string for the viewer window based on the data jalview 131 * knows about 132 * 133 * @return 134 */ getViewerTitle()135 public String getViewerTitle() 136 { 137 return getViewerTitle("Jmol", true); 138 } 139 140 /** 141 * prepare the view for a given set of models/chains. chainList contains 142 * strings of the form 'pdbfilename:Chaincode' 143 * 144 * @param chainList 145 * list of chains to make visible 146 */ centerViewer(Vector<String> chainList)147 public void centerViewer(Vector<String> chainList) 148 { 149 StringBuilder cmd = new StringBuilder(128); 150 int mlength, p; 151 for (String lbl : chainList) 152 { 153 mlength = 0; 154 do 155 { 156 p = mlength; 157 mlength = lbl.indexOf(":", p); 158 } while (p < mlength && mlength < (lbl.length() - 2)); 159 // TODO: lookup each pdb id and recover proper model number for it. 160 cmd.append(":" + lbl.substring(mlength + 1) + " /" 161 + (1 + getModelNum(chainFile.get(lbl))) + " or "); 162 } 163 if (cmd.length() > 0) 164 { 165 cmd.setLength(cmd.length() - 4); 166 } 167 evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd); 168 } 169 closeViewer()170 public void closeViewer() 171 { 172 // remove listeners for all structures in viewer 173 getSsm().removeStructureViewerListener(this, this.getStructureFiles()); 174 viewer.dispose(); 175 lastCommand = null; 176 viewer = null; 177 releaseUIResources(); 178 } 179 180 @Override colourByChain()181 public void colourByChain() 182 { 183 colourBySequence = false; 184 // TODO: colour by chain should colour each chain distinctly across all 185 // visible models 186 // TODO: http://issues.jalview.org/browse/JAL-628 187 evalStateCommand("select *;color chain"); 188 } 189 190 @Override colourByCharge()191 public void colourByCharge() 192 { 193 colourBySequence = false; 194 evalStateCommand("select *;color white;select ASP,GLU;color red;" 195 + "select LYS,ARG;color blue;select CYS;color yellow"); 196 } 197 198 /** 199 * superpose the structures associated with sequences in the alignment 200 * according to their corresponding positions. 201 */ superposeStructures(AlignmentI alignment)202 public void superposeStructures(AlignmentI alignment) 203 { 204 superposeStructures(alignment, -1, null); 205 } 206 207 /** 208 * superpose the structures associated with sequences in the alignment 209 * according to their corresponding positions. ded) 210 * 211 * @param refStructure 212 * - select which pdb file to use as reference (default is -1 - the 213 * first structure in the alignment) 214 */ superposeStructures(AlignmentI alignment, int refStructure)215 public void superposeStructures(AlignmentI alignment, int refStructure) 216 { 217 superposeStructures(alignment, refStructure, null); 218 } 219 220 /** 221 * superpose the structures associated with sequences in the alignment 222 * according to their corresponding positions. ded) 223 * 224 * @param refStructure 225 * - select which pdb file to use as reference (default is -1 - the 226 * first structure in the alignment) 227 * @param hiddenCols 228 * TODO 229 */ superposeStructures(AlignmentI alignment, int refStructure, HiddenColumns hiddenCols)230 public void superposeStructures(AlignmentI alignment, int refStructure, 231 HiddenColumns hiddenCols) 232 { 233 superposeStructures(new AlignmentI[] { alignment }, 234 new int[] 235 { refStructure }, new HiddenColumns[] { hiddenCols }); 236 } 237 238 /** 239 * {@inheritDoc} 240 */ 241 @Override superposeStructures(AlignmentI[] _alignment, int[] _refStructure, HiddenColumns[] _hiddenCols)242 public String superposeStructures(AlignmentI[] _alignment, 243 int[] _refStructure, HiddenColumns[] _hiddenCols) 244 { 245 while (viewer.isScriptExecuting()) 246 { 247 try 248 { 249 Thread.sleep(10); 250 } catch (InterruptedException i) 251 { 252 } 253 } 254 255 /* 256 * get the distinct structure files modelled 257 * (a file with multiple chains may map to multiple sequences) 258 */ 259 String[] files = getStructureFiles(); 260 if (!waitForFileLoad(files)) 261 { 262 return null; 263 } 264 265 StringBuilder selectioncom = new StringBuilder(256); 266 // In principle - nSeconds specifies the speed of animation for each 267 // superposition - but is seems to behave weirdly, so we don't specify it. 268 String nSeconds = " "; 269 if (files.length > 10) 270 { 271 nSeconds = " 0.005 "; 272 } 273 else 274 { 275 nSeconds = " " + (2.0 / files.length) + " "; 276 // if (nSeconds).substring(0,5)+" "; 277 } 278 279 // see JAL-1345 - should really automatically turn off the animation for 280 // large numbers of structures, but Jmol doesn't seem to allow that. 281 // nSeconds = " "; 282 // union of all aligned positions are collected together. 283 for (int a = 0; a < _alignment.length; a++) 284 { 285 int refStructure = _refStructure[a]; 286 AlignmentI alignment = _alignment[a]; 287 HiddenColumns hiddenCols = _hiddenCols[a]; 288 if (a > 0 && selectioncom.length() > 0 && !selectioncom 289 .substring(selectioncom.length() - 1).equals("|")) 290 { 291 selectioncom.append("|"); 292 } 293 // process this alignment 294 if (refStructure >= files.length) 295 { 296 System.err.println( 297 "Invalid reference structure value " + refStructure); 298 refStructure = -1; 299 } 300 301 /* 302 * 'matched' bit j will be set for visible alignment columns j where 303 * all sequences have a residue with a mapping to the PDB structure 304 */ 305 BitSet matched = new BitSet(); 306 for (int m = 0; m < alignment.getWidth(); m++) 307 { 308 if (hiddenCols == null || hiddenCols.isVisible(m)) 309 { 310 matched.set(m); 311 } 312 } 313 314 SuperposeData[] structures = new SuperposeData[files.length]; 315 for (int f = 0; f < files.length; f++) 316 { 317 structures[f] = new SuperposeData(alignment.getWidth()); 318 } 319 320 /* 321 * Calculate the superposable alignment columns ('matched'), and the 322 * corresponding structure residue positions (structures.pdbResNo) 323 */ 324 int candidateRefStructure = findSuperposableResidues(alignment, 325 matched, structures); 326 if (refStructure < 0) 327 { 328 /* 329 * If no reference structure was specified, pick the first one that has 330 * a mapping in the alignment 331 */ 332 refStructure = candidateRefStructure; 333 } 334 335 String[] selcom = new String[files.length]; 336 int nmatched = matched.cardinality(); 337 if (nmatched < 4) 338 { 339 return (MessageManager.formatMessage("label.insufficient_residues", 340 nmatched)); 341 } 342 343 /* 344 * generate select statements to select regions to superimpose structures 345 */ 346 { 347 // TODO extract method to construct selection statements 348 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) 349 { 350 String chainCd = ":" + structures[pdbfnum].chain; 351 int lpos = -1; 352 boolean run = false; 353 StringBuilder molsel = new StringBuilder(); 354 molsel.append("{"); 355 356 int nextColumnMatch = matched.nextSetBit(0); 357 while (nextColumnMatch != -1) 358 { 359 int pdbResNo = structures[pdbfnum].pdbResNo[nextColumnMatch]; 360 if (lpos != pdbResNo - 1) 361 { 362 // discontinuity 363 if (lpos != -1) 364 { 365 molsel.append(lpos); 366 molsel.append(chainCd); 367 molsel.append("|"); 368 } 369 run = false; 370 } 371 else 372 { 373 // continuous run - and lpos >-1 374 if (!run) 375 { 376 // at the beginning, so add dash 377 molsel.append(lpos); 378 molsel.append("-"); 379 } 380 run = true; 381 } 382 lpos = pdbResNo; 383 nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1); 384 } 385 /* 386 * add final selection phrase 387 */ 388 if (lpos != -1) 389 { 390 molsel.append(lpos); 391 molsel.append(chainCd); 392 molsel.append("}"); 393 } 394 if (molsel.length() > 1) 395 { 396 selcom[pdbfnum] = molsel.toString(); 397 selectioncom.append("(("); 398 selectioncom.append(selcom[pdbfnum].substring(1, 399 selcom[pdbfnum].length() - 1)); 400 selectioncom.append(" )& "); 401 selectioncom.append(pdbfnum + 1); 402 selectioncom.append(".1)"); 403 if (pdbfnum < files.length - 1) 404 { 405 selectioncom.append("|"); 406 } 407 } 408 else 409 { 410 selcom[pdbfnum] = null; 411 } 412 } 413 } 414 StringBuilder command = new StringBuilder(256); 415 // command.append("set spinFps 10;\n"); 416 417 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) 418 { 419 if (pdbfnum == refStructure || selcom[pdbfnum] == null 420 || selcom[refStructure] == null) 421 { 422 continue; 423 } 424 command.append("echo "); 425 command.append("\"Superposing ("); 426 command.append(structures[pdbfnum].pdbId); 427 command.append(") against reference ("); 428 command.append(structures[refStructure].pdbId); 429 command.append(")\";\ncompare " + nSeconds); 430 command.append("{"); 431 command.append(Integer.toString(1 + pdbfnum)); 432 command.append(".1} {"); 433 command.append(Integer.toString(1 + refStructure)); 434 // conformation=1 excludes alternate locations for CA (JAL-1757) 435 command.append( 436 ".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS "); 437 438 // for (int s = 0; s < 2; s++) 439 // { 440 // command.append(selcom[(s == 0 ? pdbfnum : refStructure)]); 441 // } 442 command.append(selcom[pdbfnum]); 443 command.append(selcom[refStructure]); 444 command.append(" ROTATE TRANSLATE;\n"); 445 } 446 if (selectioncom.length() > 0) 447 { 448 // TODO is performing selectioncom redundant here? is done later on 449 // System.out.println("Select regions:\n" + selectioncom.toString()); 450 evalStateCommand("select *; cartoons off; backbone; select (" 451 + selectioncom.toString() + "); cartoons; "); 452 // selcom.append("; ribbons; "); 453 String cmdString = command.toString(); 454 // System.out.println("Superimpose command(s):\n" + cmdString); 455 456 evalStateCommand(cmdString); 457 } 458 } 459 if (selectioncom.length() > 0) 460 {// finally, mark all regions that were superposed. 461 if (selectioncom.substring(selectioncom.length() - 1).equals("|")) 462 { 463 selectioncom.setLength(selectioncom.length() - 1); 464 } 465 // System.out.println("Select regions:\n" + selectioncom.toString()); 466 evalStateCommand("select *; cartoons off; backbone; select (" 467 + selectioncom.toString() + "); cartoons; "); 468 // evalStateCommand("select *; backbone; select "+selcom.toString()+"; 469 // cartoons; center "+selcom.toString()); 470 } 471 472 return null; 473 } 474 evalStateCommand(String command)475 public void evalStateCommand(String command) 476 { 477 jmolHistory(false); 478 if (lastCommand == null || !lastCommand.equals(command)) 479 { 480 viewer.evalStringQuiet(command + "\n"); 481 } 482 jmolHistory(true); 483 lastCommand = command; 484 } 485 486 Thread colourby = null; 487 488 /** 489 * Sends a set of colour commands to the structure viewer 490 * 491 * @param colourBySequenceCommands 492 */ 493 @Override colourBySequence( final StructureMappingcommandSet[] colourBySequenceCommands)494 protected void colourBySequence( 495 final StructureMappingcommandSet[] colourBySequenceCommands) 496 { 497 if (colourby != null) 498 { 499 colourby.interrupt(); 500 colourby = null; 501 } 502 colourby = new Thread(new Runnable() 503 { 504 @Override 505 public void run() 506 { 507 for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands) 508 { 509 for (String cbyseq : cpdbbyseq.commands) 510 { 511 executeWhenReady(cbyseq); 512 } 513 } 514 } 515 }); 516 colourby.start(); 517 } 518 519 /** 520 * @param files 521 * @param sr 522 * @param viewPanel 523 * @return 524 */ 525 @Override getColourBySequenceCommands( String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)526 protected StructureMappingcommandSet[] getColourBySequenceCommands( 527 String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel) 528 { 529 return JmolCommands.getColourBySequenceCommand(getSsm(), files, 530 getSequence(), sr, viewPanel); 531 } 532 533 /** 534 * @param command 535 */ executeWhenReady(String command)536 protected void executeWhenReady(String command) 537 { 538 evalStateCommand(command); 539 } 540 createImage(String file, String type, int quality)541 public void createImage(String file, String type, int quality) 542 { 543 System.out.println("JMOL CREATE IMAGE"); 544 } 545 546 @Override createImage(String fileName, String type, Object textOrBytes, int quality)547 public String createImage(String fileName, String type, 548 Object textOrBytes, int quality) 549 { 550 System.out.println("JMOL CREATE IMAGE"); 551 return null; 552 } 553 554 @Override eval(String strEval)555 public String eval(String strEval) 556 { 557 // System.out.println(strEval); 558 // "# 'eval' is implemented only for the applet."; 559 return null; 560 } 561 562 // End StructureListener 563 // ////////////////////////// 564 565 @Override functionXY(String functionName, int x, int y)566 public float[][] functionXY(String functionName, int x, int y) 567 { 568 return null; 569 } 570 571 @Override functionXYZ(String functionName, int nx, int ny, int nz)572 public float[][][] functionXYZ(String functionName, int nx, int ny, 573 int nz) 574 { 575 // TODO Auto-generated method stub 576 return null; 577 } 578 getColour(int atomIndex, int pdbResNum, String chain, String pdbfile)579 public Color getColour(int atomIndex, int pdbResNum, String chain, 580 String pdbfile) 581 { 582 if (getModelNum(pdbfile) < 0) 583 { 584 return null; 585 } 586 // TODO: verify atomIndex is selecting correct model. 587 // return new Color(viewer.getAtomArgb(atomIndex)); Jmol 12.2.4 588 int colour = viewer.ms.at[atomIndex].atomPropertyInt(T.color); 589 return new Color(colour); 590 } 591 592 /** 593 * instruct the Jalview binding to update the pdbentries vector if necessary 594 * prior to matching the jmol view's contents to the list of structure files 595 * Jalview knows about. 596 */ refreshPdbEntries()597 public abstract void refreshPdbEntries(); 598 getModelNum(String modelFileName)599 private int getModelNum(String modelFileName) 600 { 601 String[] mfn = getStructureFiles(); 602 if (mfn == null) 603 { 604 return -1; 605 } 606 for (int i = 0; i < mfn.length; i++) 607 { 608 if (mfn[i].equalsIgnoreCase(modelFileName)) 609 { 610 return i; 611 } 612 } 613 return -1; 614 } 615 616 /** 617 * map between index of model filename returned from getPdbFile and the first 618 * index of models from this file in the viewer. Note - this is not trimmed - 619 * use getPdbFile to get number of unique models. 620 */ 621 private int _modelFileNameMap[]; 622 623 @Override getStructureFiles()624 public synchronized String[] getStructureFiles() 625 { 626 List<String> mset = new ArrayList<>(); 627 if (viewer == null) 628 { 629 return new String[0]; 630 } 631 632 if (modelFileNames == null) 633 { 634 int modelCount = viewer.ms.mc; 635 String filePath = null; 636 for (int i = 0; i < modelCount; ++i) 637 { 638 filePath = viewer.ms.getModelFileName(i); 639 if (!mset.contains(filePath)) 640 { 641 mset.add(filePath); 642 } 643 } 644 modelFileNames = mset.toArray(new String[mset.size()]); 645 } 646 647 return modelFileNames; 648 } 649 650 /** 651 * map from string to applet 652 */ 653 @Override getRegistryInfo()654 public Map<String, Object> getRegistryInfo() 655 { 656 // TODO Auto-generated method stub 657 return null; 658 } 659 660 // /////////////////////////////// 661 // JmolStatusListener 662 handlePopupMenu(int x, int y)663 public void handlePopupMenu(int x, int y) 664 { 665 // jmolpopup.show(x, y); 666 // jmolpopup.jpiShow(x, y); 667 } 668 669 /** 670 * Highlight zero, one or more atoms on the structure 671 */ 672 @Override highlightAtoms(List<AtomSpec> atoms)673 public void highlightAtoms(List<AtomSpec> atoms) 674 { 675 if (atoms != null) 676 { 677 if (resetLastRes.length() > 0) 678 { 679 viewer.evalStringQuiet(resetLastRes.toString()); 680 resetLastRes.setLength(0); 681 } 682 for (AtomSpec atom : atoms) 683 { 684 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(), 685 atom.getChain(), atom.getPdbFile()); 686 } 687 } 688 } 689 690 // jmol/ssm only highlightAtom(int atomIndex, int pdbResNum, String chain, String pdbfile)691 public void highlightAtom(int atomIndex, int pdbResNum, String chain, 692 String pdbfile) 693 { 694 if (modelFileNames == null) 695 { 696 return; 697 } 698 699 // look up file model number for this pdbfile 700 int mdlNum = 0; 701 // may need to adjust for URLencoding here - we don't worry about that yet. 702 while (mdlNum < modelFileNames.length 703 && !pdbfile.equals(modelFileNames[mdlNum])) 704 { 705 mdlNum++; 706 } 707 if (mdlNum == modelFileNames.length) 708 { 709 return; 710 } 711 712 jmolHistory(false); 713 714 StringBuilder cmd = new StringBuilder(64); 715 cmd.append("select " + pdbResNum); // +modelNum 716 717 resetLastRes.append("select " + pdbResNum); // +modelNum 718 719 cmd.append(":"); 720 resetLastRes.append(":"); 721 if (!chain.equals(" ")) 722 { 723 cmd.append(chain); 724 resetLastRes.append(chain); 725 } 726 { 727 cmd.append(" /" + (mdlNum + 1)); 728 resetLastRes.append("/" + (mdlNum + 1)); 729 } 730 cmd.append(";wireframe 100;" + cmd.toString() + " and not hetero;"); 731 732 resetLastRes.append(";wireframe 0;" + resetLastRes.toString() 733 + " and not hetero; spacefill 0;"); 734 735 cmd.append("spacefill 200;select none"); 736 737 viewer.evalStringQuiet(cmd.toString()); 738 jmolHistory(true); 739 740 } 741 742 boolean debug = true; 743 jmolHistory(boolean enable)744 private void jmolHistory(boolean enable) 745 { 746 viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off")); 747 } 748 loadInline(String string)749 public void loadInline(String string) 750 { 751 loadedInline = true; 752 // TODO: re JAL-623 753 // viewer.loadInline(strModel, isAppend); 754 // could do this: 755 // construct fake fullPathName and fileName so we can identify the file 756 // later. 757 // Then, construct pass a reader for the string to Jmol. 758 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName, 759 // fileName, null, reader, false, null, null, 0); 760 viewer.openStringInline(string); 761 } 762 mouseOverStructure(int atomIndex, final String strInfo)763 protected void mouseOverStructure(int atomIndex, final String strInfo) 764 { 765 int pdbResNum; 766 int alocsep = strInfo.indexOf("^"); 767 int mdlSep = strInfo.indexOf("/"); 768 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1; 769 770 if (chainSeparator == -1) 771 { 772 chainSeparator = strInfo.indexOf("."); 773 if (mdlSep > -1 && mdlSep < chainSeparator) 774 { 775 chainSeparator1 = chainSeparator; 776 chainSeparator = mdlSep; 777 } 778 } 779 // handle insertion codes 780 if (alocsep != -1) 781 { 782 pdbResNum = Integer.parseInt( 783 strInfo.substring(strInfo.indexOf("]") + 1, alocsep)); 784 785 } 786 else 787 { 788 pdbResNum = Integer.parseInt( 789 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator)); 790 } 791 String chainId; 792 793 if (strInfo.indexOf(":") > -1) 794 { 795 chainId = strInfo.substring(strInfo.indexOf(":") + 1, 796 strInfo.indexOf(".")); 797 } 798 else 799 { 800 chainId = " "; 801 } 802 803 String pdbfilename = modelFileNames[frameNo]; // default is first or current 804 // model 805 if (mdlSep > -1) 806 { 807 if (chainSeparator1 == -1) 808 { 809 chainSeparator1 = strInfo.indexOf(".", mdlSep); 810 } 811 String mdlId = (chainSeparator1 > -1) 812 ? strInfo.substring(mdlSep + 1, chainSeparator1) 813 : strInfo.substring(mdlSep + 1); 814 try 815 { 816 // recover PDB filename for the model hovered over. 817 int mnumber = Integer.valueOf(mdlId).intValue() - 1; 818 if (_modelFileNameMap != null) 819 { 820 int _mp = _modelFileNameMap.length - 1; 821 822 while (mnumber < _modelFileNameMap[_mp]) 823 { 824 _mp--; 825 } 826 pdbfilename = modelFileNames[_mp]; 827 } 828 else 829 { 830 if (mnumber >= 0 && mnumber < modelFileNames.length) 831 { 832 pdbfilename = modelFileNames[mnumber]; 833 } 834 835 if (pdbfilename == null) 836 { 837 pdbfilename = new File(viewer.ms.getModelFileName(mnumber)) 838 .getAbsolutePath(); 839 } 840 } 841 } catch (Exception e) 842 { 843 } 844 } 845 846 /* 847 * highlight position on alignment(s); if some text is returned, 848 * show this as a second line on the structure hover tooltip 849 */ 850 String label = getSsm().mouseOverStructure(pdbResNum, chainId, 851 pdbfilename); 852 if (label != null) 853 { 854 // change comma to pipe separator (newline token for Jmol) 855 label = label.replace(',', '|'); 856 StringTokenizer toks = new StringTokenizer(strInfo, " "); 857 StringBuilder sb = new StringBuilder(); 858 sb.append("select ").append(String.valueOf(pdbResNum)).append(":") 859 .append(chainId).append("/1"); 860 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ") 861 .append(toks.nextToken()); 862 sb.append("|").append(label).append("\""); 863 evalStateCommand(sb.toString()); 864 } 865 } 866 notifyAtomHovered(int atomIndex, String strInfo, String data)867 public void notifyAtomHovered(int atomIndex, String strInfo, String data) 868 { 869 if (strInfo.equals(lastMessage)) 870 { 871 return; 872 } 873 lastMessage = strInfo; 874 if (data != null) 875 { 876 System.err.println("Ignoring additional hover info: " + data 877 + " (other info: '" + strInfo + "' pos " + atomIndex + ")"); 878 } 879 mouseOverStructure(atomIndex, strInfo); 880 } 881 882 /* 883 * { if (history != null && strStatus != null && 884 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); 885 * } } 886 */ 887 notifyAtomPicked(int atomIndex, String strInfo, String strData)888 public void notifyAtomPicked(int atomIndex, String strInfo, 889 String strData) 890 { 891 /** 892 * this implements the toggle label behaviour copied from the original 893 * structure viewer, MCView 894 */ 895 if (strData != null) 896 { 897 System.err.println("Ignoring additional pick data string " + strData); 898 } 899 int chainSeparator = strInfo.indexOf(":"); 900 int p = 0; 901 if (chainSeparator == -1) 902 { 903 chainSeparator = strInfo.indexOf("."); 904 } 905 906 String picked = strInfo.substring(strInfo.indexOf("]") + 1, 907 chainSeparator); 908 String mdlString = ""; 909 if ((p = strInfo.indexOf(":")) > -1) 910 { 911 picked += strInfo.substring(p, strInfo.indexOf(".")); 912 } 913 914 if ((p = strInfo.indexOf("/")) > -1) 915 { 916 mdlString += strInfo.substring(p, strInfo.indexOf(" #")); 917 } 918 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P" 919 + mdlString + "))"; 920 jmolHistory(false); 921 922 if (!atomsPicked.contains(picked)) 923 { 924 viewer.evalStringQuiet("select " + picked + ";label %n %r:%c"); 925 atomsPicked.addElement(picked); 926 } 927 else 928 { 929 viewer.evalString("select " + picked + ";label off"); 930 atomsPicked.removeElement(picked); 931 } 932 jmolHistory(true); 933 // TODO: in application this happens 934 // 935 // if (scriptWindow != null) 936 // { 937 // scriptWindow.sendConsoleMessage(strInfo); 938 // scriptWindow.sendConsoleMessage("\n"); 939 // } 940 941 } 942 943 @Override notifyCallback(CBK type, Object[] data)944 public void notifyCallback(CBK type, Object[] data) 945 { 946 /* 947 * ensure processed in AWT thread to avoid risk of deadlocks 948 */ 949 SwingUtilities.invokeLater(new Runnable() 950 { 951 952 @Override 953 public void run() 954 { 955 processCallback(type, data); 956 } 957 }); 958 } 959 960 /** 961 * Processes one callback notification from Jmol 962 * 963 * @param type 964 * @param data 965 */ processCallback(CBK type, Object[] data)966 protected void processCallback(CBK type, Object[] data) 967 { 968 try 969 { 970 switch (type) 971 { 972 case LOADSTRUCT: 973 notifyFileLoaded((String) data[1], (String) data[2], 974 (String) data[3], (String) data[4], 975 ((Integer) data[5]).intValue()); 976 977 break; 978 case PICK: 979 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1], 980 (String) data[0]); 981 // also highlight in alignment 982 // deliberate fall through 983 case HOVER: 984 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1], 985 (String) data[0]); 986 break; 987 case SCRIPT: 988 notifyScriptTermination((String) data[2], 989 ((Integer) data[3]).intValue()); 990 break; 991 case ECHO: 992 sendConsoleEcho((String) data[1]); 993 break; 994 case MESSAGE: 995 sendConsoleMessage( 996 (data == null) ? ((String) null) : (String) data[1]); 997 break; 998 case ERROR: 999 // System.err.println("Ignoring error callback."); 1000 break; 1001 case SYNC: 1002 case RESIZE: 1003 refreshGUI(); 1004 break; 1005 case MEASURE: 1006 1007 case CLICK: 1008 default: 1009 System.err.println( 1010 "Unhandled callback " + type + " " + data[1].toString()); 1011 break; 1012 } 1013 } catch (Exception e) 1014 { 1015 System.err.println("Squashed Jmol callback handler error:"); 1016 e.printStackTrace(); 1017 } 1018 } 1019 1020 @Override notifyEnabled(CBK callbackPick)1021 public boolean notifyEnabled(CBK callbackPick) 1022 { 1023 switch (callbackPick) 1024 { 1025 case ECHO: 1026 case LOADSTRUCT: 1027 case MEASURE: 1028 case MESSAGE: 1029 case PICK: 1030 case SCRIPT: 1031 case HOVER: 1032 case ERROR: 1033 return true; 1034 default: 1035 return false; 1036 } 1037 } 1038 1039 // incremented every time a load notification is successfully handled - 1040 // lightweight mechanism for other threads to detect when they can start 1041 // referrring to new structures. 1042 private long loadNotifiesHandled = 0; 1043 getLoadNotifiesHandled()1044 public long getLoadNotifiesHandled() 1045 { 1046 return loadNotifiesHandled; 1047 } 1048 notifyFileLoaded(String fullPathName, String fileName2, String modelName, String errorMsg, int modelParts)1049 public void notifyFileLoaded(String fullPathName, String fileName2, 1050 String modelName, String errorMsg, int modelParts) 1051 { 1052 if (errorMsg != null) 1053 { 1054 fileLoadingError = errorMsg; 1055 refreshGUI(); 1056 return; 1057 } 1058 // TODO: deal sensibly with models loaded inLine: 1059 // modelName will be null, as will fullPathName. 1060 1061 // the rest of this routine ignores the arguments, and simply interrogates 1062 // the Jmol view to find out what structures it contains, and adds them to 1063 // the structure selection manager. 1064 fileLoadingError = null; 1065 String[] oldmodels = modelFileNames; 1066 modelFileNames = null; 1067 chainNames = new ArrayList<>(); 1068 chainFile = new Hashtable<>(); 1069 boolean notifyLoaded = false; 1070 String[] modelfilenames = getStructureFiles(); 1071 // first check if we've lost any structures 1072 if (oldmodels != null && oldmodels.length > 0) 1073 { 1074 int oldm = 0; 1075 for (int i = 0; i < oldmodels.length; i++) 1076 { 1077 for (int n = 0; n < modelfilenames.length; n++) 1078 { 1079 if (modelfilenames[n] == oldmodels[i]) 1080 { 1081 oldmodels[i] = null; 1082 break; 1083 } 1084 } 1085 if (oldmodels[i] != null) 1086 { 1087 oldm++; 1088 } 1089 } 1090 if (oldm > 0) 1091 { 1092 String[] oldmfn = new String[oldm]; 1093 oldm = 0; 1094 for (int i = 0; i < oldmodels.length; i++) 1095 { 1096 if (oldmodels[i] != null) 1097 { 1098 oldmfn[oldm++] = oldmodels[i]; 1099 } 1100 } 1101 // deregister the Jmol instance for these structures - we'll add 1102 // ourselves again at the end for the current structure set. 1103 getSsm().removeStructureViewerListener(this, oldmfn); 1104 } 1105 } 1106 refreshPdbEntries(); 1107 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++) 1108 { 1109 String fileName = modelfilenames[modelnum]; 1110 boolean foundEntry = false; 1111 StructureFile pdb = null; 1112 String pdbfile = null; 1113 // model was probably loaded inline - so check the pdb file hashcode 1114 if (loadedInline) 1115 { 1116 // calculate essential attributes for the pdb data imported inline. 1117 // prolly need to resolve modelnumber properly - for now just use our 1118 // 'best guess' 1119 pdbfile = viewer.getData( 1120 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB"); 1121 } 1122 // search pdbentries and sequences to find correct pdbentry for this 1123 // model 1124 for (int pe = 0; pe < getPdbCount(); pe++) 1125 { 1126 boolean matches = false; 1127 addSequence(pe, getSequence()[pe]); 1128 if (fileName == null) 1129 { 1130 if (false) 1131 // see JAL-623 - need method of matching pasted data up 1132 { 1133 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], 1134 pdbfile, DataSourceType.PASTE, getIProgressIndicator()); 1135 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId()); 1136 matches = true; 1137 foundEntry = true; 1138 } 1139 } 1140 else 1141 { 1142 File fl = new File(getPdbEntry(pe).getFile()); 1143 matches = fl.equals(new File(fileName)); 1144 if (matches) 1145 { 1146 foundEntry = true; 1147 // TODO: Jmol can in principle retrieve from CLASSLOADER but 1148 // this 1149 // needs 1150 // to be tested. See mantis bug 1151 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605 1152 DataSourceType protocol = DataSourceType.URL; 1153 try 1154 { 1155 if (fl.exists()) 1156 { 1157 protocol = DataSourceType.FILE; 1158 } 1159 } catch (Exception e) 1160 { 1161 } catch (Error e) 1162 { 1163 } 1164 // Explicitly map to the filename used by Jmol ; 1165 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe], 1166 fileName, protocol, getIProgressIndicator()); 1167 // pdbentry[pe].getFile(), protocol); 1168 1169 } 1170 } 1171 if (matches) 1172 { 1173 // add an entry for every chain in the model 1174 for (int i = 0; i < pdb.getChains().size(); i++) 1175 { 1176 String chid = new String( 1177 pdb.getId() + ":" + pdb.getChains().elementAt(i).id); 1178 chainFile.put(chid, fileName); 1179 chainNames.add(chid); 1180 } 1181 notifyLoaded = true; 1182 } 1183 } 1184 1185 if (!foundEntry && associateNewStructs) 1186 { 1187 // this is a foreign pdb file that jalview doesn't know about - add 1188 // it to the dataset and try to find a home - either on a matching 1189 // sequence or as a new sequence. 1190 String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1", 1191 "PDB"); 1192 // parse pdb file into a chain, etc. 1193 // locate best match for pdb in associated views and add mapping to 1194 // ssm 1195 // if properly registered then 1196 notifyLoaded = true; 1197 1198 } 1199 } 1200 // FILE LOADED OK 1201 // so finally, update the jmol bits and pieces 1202 // if (jmolpopup != null) 1203 // { 1204 // // potential for deadlock here: 1205 // // jmolpopup.updateComputedMenus(); 1206 // } 1207 if (!isLoadingFromArchive()) 1208 { 1209 viewer.evalStringQuiet( 1210 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off"); 1211 } 1212 // register ourselves as a listener and notify the gui that it needs to 1213 // update itself. 1214 getSsm().addStructureViewerListener(this); 1215 if (notifyLoaded) 1216 { 1217 FeatureRenderer fr = getFeatureRenderer(null); 1218 if (fr != null) 1219 { 1220 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme(); 1221 ((AppJmol) getViewer()).getAlignmentPanel().av 1222 .applyFeaturesStyle(colours); 1223 } 1224 refreshGUI(); 1225 loadNotifiesHandled++; 1226 } 1227 setLoadingFromArchive(false); 1228 } 1229 1230 @Override getChainNames()1231 public List<String> getChainNames() 1232 { 1233 return chainNames; 1234 } 1235 getIProgressIndicator()1236 protected IProgressIndicator getIProgressIndicator() 1237 { 1238 return null; 1239 } 1240 notifyNewPickingModeMeasurement(int iatom, String strMeasure)1241 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure) 1242 { 1243 notifyAtomPicked(iatom, strMeasure, null); 1244 } 1245 notifyScriptTermination(String strStatus, int msWalltime)1246 public abstract void notifyScriptTermination(String strStatus, 1247 int msWalltime); 1248 1249 /** 1250 * display a message echoed from the jmol viewer 1251 * 1252 * @param strEcho 1253 */ sendConsoleEcho(String strEcho)1254 public abstract void sendConsoleEcho(String strEcho); /* 1255 * { showConsole(true); 1256 * 1257 * history.append("\n" + 1258 * strEcho); } 1259 */ 1260 1261 // /End JmolStatusListener 1262 // ///////////////////////////// 1263 1264 /** 1265 * @param strStatus 1266 * status message - usually the response received after a script 1267 * executed 1268 */ sendConsoleMessage(String strStatus)1269 public abstract void sendConsoleMessage(String strStatus); 1270 1271 @Override setCallbackFunction(String callbackType, String callbackFunction)1272 public void setCallbackFunction(String callbackType, 1273 String callbackFunction) 1274 { 1275 System.err.println("Ignoring set-callback request to associate " 1276 + callbackType + " with function " + callbackFunction); 1277 1278 } 1279 1280 @Override setJalviewColourScheme(ColourSchemeI cs)1281 public void setJalviewColourScheme(ColourSchemeI cs) 1282 { 1283 colourBySequence = false; 1284 1285 if (cs == null) 1286 { 1287 return; 1288 } 1289 1290 jmolHistory(false); 1291 StringBuilder command = new StringBuilder(128); 1292 command.append("select *;color white;"); 1293 List<String> residueSet = ResidueProperties.getResidues(isNucleotide(), 1294 false); 1295 for (String resName : residueSet) 1296 { 1297 char res = resName.length() == 3 1298 ? ResidueProperties.getSingleCharacterCode(resName) 1299 : resName.charAt(0); 1300 Color col = cs.findColour(res, 0, null, null, 0f); 1301 command.append("select " + resName + ";color[" + col.getRed() + "," 1302 + col.getGreen() + "," + col.getBlue() + "];"); 1303 } 1304 1305 evalStateCommand(command.toString()); 1306 jmolHistory(true); 1307 } 1308 showHelp()1309 public void showHelp() 1310 { 1311 showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp"); 1312 } 1313 1314 /** 1315 * open the URL somehow 1316 * 1317 * @param target 1318 */ showUrl(String url, String target)1319 public abstract void showUrl(String url, String target); 1320 1321 /** 1322 * called when the binding thinks the UI needs to be refreshed after a Jmol 1323 * state change. this could be because structures were loaded, or because an 1324 * error has occured. 1325 */ refreshGUI()1326 public abstract void refreshGUI(); 1327 1328 /** 1329 * called to show or hide the associated console window container. 1330 * 1331 * @param show 1332 */ showConsole(boolean show)1333 public abstract void showConsole(boolean show); 1334 1335 /** 1336 * @param renderPanel 1337 * @param jmolfileio 1338 * - when true will initialise jmol's file IO system (should be false 1339 * in applet context) 1340 * @param htmlName 1341 * @param documentBase 1342 * @param codeBase 1343 * @param commandOptions 1344 */ allocateViewer(Container renderPanel, boolean jmolfileio, String htmlName, URL documentBase, URL codeBase, String commandOptions)1345 public void allocateViewer(Container renderPanel, boolean jmolfileio, 1346 String htmlName, URL documentBase, URL codeBase, 1347 String commandOptions) 1348 { 1349 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase, 1350 codeBase, commandOptions, null, null); 1351 } 1352 1353 /** 1354 * 1355 * @param renderPanel 1356 * @param jmolfileio 1357 * - when true will initialise jmol's file IO system (should be false 1358 * in applet context) 1359 * @param htmlName 1360 * @param documentBase 1361 * @param codeBase 1362 * @param commandOptions 1363 * @param consolePanel 1364 * - panel to contain Jmol console 1365 * @param buttonsToShow 1366 * - buttons to show on the console, in ordr 1367 */ allocateViewer(Container renderPanel, boolean jmolfileio, String htmlName, URL documentBase, URL codeBase, String commandOptions, final Container consolePanel, String buttonsToShow)1368 public void allocateViewer(Container renderPanel, boolean jmolfileio, 1369 String htmlName, URL documentBase, URL codeBase, 1370 String commandOptions, final Container consolePanel, 1371 String buttonsToShow) 1372 { 1373 if (commandOptions == null) 1374 { 1375 commandOptions = ""; 1376 } 1377 viewer = (Viewer) JmolViewer.allocateViewer(renderPanel, 1378 (jmolfileio ? new SmarterJmolAdapter() : null), 1379 htmlName + ((Object) this).toString(), documentBase, codeBase, 1380 commandOptions, this); 1381 1382 viewer.setJmolStatusListener(this); // extends JmolCallbackListener 1383 1384 console = createJmolConsole(consolePanel, buttonsToShow); 1385 if (consolePanel != null) 1386 { 1387 consolePanel.addComponentListener(this); 1388 1389 } 1390 1391 } 1392 createJmolConsole( Container consolePanel, String buttonsToShow)1393 protected abstract JmolAppConsoleInterface createJmolConsole( 1394 Container consolePanel, String buttonsToShow); 1395 1396 protected org.jmol.api.JmolAppConsoleInterface console = null; 1397 1398 @Override setBackgroundColour(java.awt.Color col)1399 public void setBackgroundColour(java.awt.Color col) 1400 { 1401 jmolHistory(false); 1402 viewer.evalStringQuiet("background [" + col.getRed() + "," 1403 + col.getGreen() + "," + col.getBlue() + "];"); 1404 jmolHistory(true); 1405 } 1406 1407 @Override resizeInnerPanel(String data)1408 public int[] resizeInnerPanel(String data) 1409 { 1410 // Jalview doesn't honour resize panel requests 1411 return null; 1412 } 1413 1414 /** 1415 * 1416 */ closeConsole()1417 protected void closeConsole() 1418 { 1419 if (console != null) 1420 { 1421 try 1422 { 1423 console.setVisible(false); 1424 } catch (Error e) 1425 { 1426 } catch (Exception x) 1427 { 1428 } 1429 ; 1430 console = null; 1431 } 1432 } 1433 1434 /** 1435 * ComponentListener method 1436 */ 1437 @Override componentMoved(ComponentEvent e)1438 public void componentMoved(ComponentEvent e) 1439 { 1440 } 1441 1442 /** 1443 * ComponentListener method 1444 */ 1445 @Override componentResized(ComponentEvent e)1446 public void componentResized(ComponentEvent e) 1447 { 1448 } 1449 1450 /** 1451 * ComponentListener method 1452 */ 1453 @Override componentShown(ComponentEvent e)1454 public void componentShown(ComponentEvent e) 1455 { 1456 showConsole(true); 1457 } 1458 1459 /** 1460 * ComponentListener method 1461 */ 1462 @Override componentHidden(ComponentEvent e)1463 public void componentHidden(ComponentEvent e) 1464 { 1465 showConsole(false); 1466 } 1467 } 1468