/*
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
* This file is part of Jalview.
*
* Jalview is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* Jalview is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jalview. If not, see .
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
package jalview.ext.jmol;
import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
import jalview.api.FeatureRenderer;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.HiddenColumns;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
import jalview.ext.rbvi.chimera.AtomSpecModel;
import jalview.gui.IProgressIndicator;
import jalview.io.DataSourceType;
import jalview.io.StructureFile;
import jalview.schemes.ColourSchemeI;
import jalview.schemes.ResidueProperties;
import jalview.structure.AtomSpec;
import jalview.structure.StructureSelectionManager;
import jalview.structures.models.AAStructureBindingModel;
import jalview.util.MessageManager;
import java.awt.Color;
import java.awt.Container;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.jmol.adapter.smarter.SmarterJmolAdapter;
import org.jmol.api.JmolAppConsoleInterface;
import org.jmol.api.JmolSelectionListener;
import org.jmol.api.JmolStatusListener;
import org.jmol.api.JmolViewer;
import org.jmol.c.CBK;
import org.jmol.script.T;
import org.jmol.viewer.Viewer;
public abstract class JalviewJmolBinding extends AAStructureBindingModel
implements JmolStatusListener, JmolSelectionListener,
ComponentListener
{
boolean allChainsSelected = false;
/*
* when true, try to search the associated datamodel for sequences that are
* associated with any unknown structures in the Jmol view.
*/
private boolean associateNewStructs = false;
Vector atomsPicked = new Vector<>();
private List chainNames;
Hashtable chainFile;
/*
* the default or current model displayed if the model cannot be identified
* from the selection message
*/
int frameNo = 0;
// protected JmolGenericPopup jmolpopup; // not used - remove?
String lastCommand;
String lastMessage;
boolean loadedInline;
StringBuffer resetLastRes = new StringBuffer();
public Viewer viewer;
public JalviewJmolBinding(StructureSelectionManager ssm,
PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
DataSourceType protocol)
{
super(ssm, pdbentry, sequenceIs, protocol);
/*
* viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
* "jalviewJmol", ap.av.applet .getDocumentBase(),
* ap.av.applet.getCodeBase(), "", this);
*
* jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
*/
}
public JalviewJmolBinding(StructureSelectionManager ssm,
SequenceI[][] seqs, Viewer theViewer)
{
super(ssm, seqs);
viewer = theViewer;
viewer.setJmolStatusListener(this);
viewer.addSelectionListener(this);
}
/**
* construct a title string for the viewer window based on the data jalview
* knows about
*
* @return
*/
public String getViewerTitle()
{
return getViewerTitle("Jmol", true);
}
/**
* prepare the view for a given set of models/chains. chainList contains
* strings of the form 'pdbfilename:Chaincode'
*/
public void centerViewer()
{
StringBuilder cmd = new StringBuilder(128);
int mlength, p;
for (String lbl : chainsToShow)
{
mlength = 0;
do
{
p = mlength;
mlength = lbl.indexOf(":", p);
} while (p < mlength && mlength < (lbl.length() - 2));
// TODO: lookup each pdb id and recover proper model number for it.
cmd.append(":" + lbl.substring(mlength + 1) + " /"
+ (1 + getModelNum(chainFile.get(lbl))) + " or ");
}
if (cmd.length() > 0)
{
cmd.setLength(cmd.length() - 4);
}
String command = "select *;restrict " + cmd + ";cartoon;center " + cmd;
evalStateCommand(command);
}
public void closeViewer()
{
// remove listeners for all structures in viewer
getSsm().removeStructureViewerListener(this, this.getStructureFiles());
viewer.dispose();
lastCommand = null;
viewer = null;
releaseUIResources();
}
@Override
public void colourByChain()
{
colourBySequence = false;
// TODO: colour by chain should colour each chain distinctly across all
// visible models
// TODO: http://issues.jalview.org/browse/JAL-628
evalStateCommand("select *;color chain");
}
@Override
public void colourByCharge()
{
colourBySequence = false;
evalStateCommand("select *;color white;select ASP,GLU;color red;"
+ "select LYS,ARG;color blue;select CYS;color yellow");
}
/**
* superpose the structures associated with sequences in the alignment
* according to their corresponding positions.
*/
public void superposeStructures(AlignmentI alignment)
{
superposeStructures(alignment, -1, null);
}
/**
* superpose the structures associated with sequences in the alignment
* according to their corresponding positions. ded)
*
* @param refStructure
* - select which pdb file to use as reference (default is -1 - the
* first structure in the alignment)
*/
public void superposeStructures(AlignmentI alignment, int refStructure)
{
superposeStructures(alignment, refStructure, null);
}
/**
* superpose the structures associated with sequences in the alignment
* according to their corresponding positions. ded)
*
* @param refStructure
* - select which pdb file to use as reference (default is -1 - the
* first structure in the alignment)
* @param hiddenCols
* TODO
*/
public void superposeStructures(AlignmentI alignment, int refStructure,
HiddenColumns hiddenCols)
{
superposeStructures(new AlignmentI[] { alignment },
new int[]
{ refStructure }, new HiddenColumns[] { hiddenCols });
}
/**
* {@inheritDoc}
*/
@Override
public String superposeStructures(AlignmentI[] _alignment,
int[] _refStructure, HiddenColumns[] _hiddenCols)
{
while (viewer.isScriptExecuting())
{
try
{
Thread.sleep(10);
} catch (InterruptedException i)
{
}
}
/*
* get the distinct structure files modelled
* (a file with multiple chains may map to multiple sequences)
*/
String[] files = getStructureFiles();
if (!waitForFileLoad(files))
{
return null;
}
StringBuilder selectioncom = new StringBuilder(256);
// In principle - nSeconds specifies the speed of animation for each
// superposition - but is seems to behave weirdly, so we don't specify it.
String nSeconds = " ";
if (files.length > 10)
{
nSeconds = " 0.005 ";
}
else
{
nSeconds = " " + (2.0 / files.length) + " ";
// if (nSeconds).substring(0,5)+" ";
}
// see JAL-1345 - should really automatically turn off the animation for
// large numbers of structures, but Jmol doesn't seem to allow that.
// nSeconds = " ";
// union of all aligned positions are collected together.
for (int a = 0; a < _alignment.length; a++)
{
int refStructure = _refStructure[a];
AlignmentI alignment = _alignment[a];
HiddenColumns hiddenCols = _hiddenCols[a];
if (a > 0 && selectioncom.length() > 0 && !selectioncom
.substring(selectioncom.length() - 1).equals("|"))
{
selectioncom.append("|");
}
// process this alignment
if (refStructure >= files.length)
{
System.err.println(
"Invalid reference structure value " + refStructure);
refStructure = -1;
}
/*
* 'matched' bit j will be set for visible alignment columns j where
* all sequences have a residue with a mapping to the PDB structure
*/
BitSet matched = new BitSet();
for (int m = 0; m < alignment.getWidth(); m++)
{
if (hiddenCols == null || hiddenCols.isVisible(m))
{
matched.set(m);
}
}
SuperposeData[] structures = new SuperposeData[files.length];
for (int f = 0; f < files.length; f++)
{
structures[f] = new SuperposeData(alignment.getWidth());
}
/*
* Calculate the superposable alignment columns ('matched'), and the
* corresponding structure residue positions (structures.pdbResNo)
*/
int candidateRefStructure = findSuperposableResidues(alignment,
matched, structures);
if (refStructure < 0)
{
/*
* If no reference structure was specified, pick the first one that has
* a mapping in the alignment
*/
refStructure = candidateRefStructure;
}
String[] selcom = new String[files.length];
int nmatched = matched.cardinality();
if (nmatched < 4)
{
return (MessageManager.formatMessage("label.insufficient_residues",
nmatched));
}
/*
* generate select statements to select regions to superimpose structures
*/
{
// TODO extract method to construct selection statements
for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
{
String chainCd = ":" + structures[pdbfnum].chain;
int lpos = -1;
boolean run = false;
StringBuilder molsel = new StringBuilder();
molsel.append("{");
int nextColumnMatch = matched.nextSetBit(0);
while (nextColumnMatch != -1)
{
int pdbResNo = structures[pdbfnum].pdbResNo[nextColumnMatch];
if (lpos != pdbResNo - 1)
{
// discontinuity
if (lpos != -1)
{
molsel.append(lpos);
molsel.append(chainCd);
molsel.append("|");
}
run = false;
}
else
{
// continuous run - and lpos >-1
if (!run)
{
// at the beginning, so add dash
molsel.append(lpos);
molsel.append("-");
}
run = true;
}
lpos = pdbResNo;
nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
}
/*
* add final selection phrase
*/
if (lpos != -1)
{
molsel.append(lpos);
molsel.append(chainCd);
molsel.append("}");
}
if (molsel.length() > 1)
{
selcom[pdbfnum] = molsel.toString();
selectioncom.append("((");
selectioncom.append(selcom[pdbfnum].substring(1,
selcom[pdbfnum].length() - 1));
selectioncom.append(" )& ");
selectioncom.append(pdbfnum + 1);
selectioncom.append(".1)");
if (pdbfnum < files.length - 1)
{
selectioncom.append("|");
}
}
else
{
selcom[pdbfnum] = null;
}
}
}
StringBuilder command = new StringBuilder(256);
// command.append("set spinFps 10;\n");
for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
{
if (pdbfnum == refStructure || selcom[pdbfnum] == null
|| selcom[refStructure] == null)
{
continue;
}
command.append("echo ");
command.append("\"Superposing (");
command.append(structures[pdbfnum].pdbId);
command.append(") against reference (");
command.append(structures[refStructure].pdbId);
command.append(")\";\ncompare " + nSeconds);
command.append("{");
command.append(Integer.toString(1 + pdbfnum));
command.append(".1} {");
command.append(Integer.toString(1 + refStructure));
// conformation=1 excludes alternate locations for CA (JAL-1757)
command.append(
".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
// for (int s = 0; s < 2; s++)
// {
// command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
// }
command.append(selcom[pdbfnum]);
command.append(selcom[refStructure]);
command.append(" ROTATE TRANSLATE;\n");
}
if (selectioncom.length() > 0)
{
// TODO is performing selectioncom redundant here? is done later on
// System.out.println("Select regions:\n" + selectioncom.toString());
evalStateCommand("select *; cartoons off; backbone; select ("
+ selectioncom.toString() + "); cartoons; ");
// selcom.append("; ribbons; ");
String cmdString = command.toString();
// System.out.println("Superimpose command(s):\n" + cmdString);
evalStateCommand(cmdString);
}
}
if (selectioncom.length() > 0)
{// finally, mark all regions that were superposed.
if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
{
selectioncom.setLength(selectioncom.length() - 1);
}
// System.out.println("Select regions:\n" + selectioncom.toString());
evalStateCommand("select *; cartoons off; backbone; select ("
+ selectioncom.toString() + "); cartoons; ");
// evalStateCommand("select *; backbone; select "+selcom.toString()+";
// cartoons; center "+selcom.toString());
}
return null;
}
public void evalStateCommand(String command)
{
jmolHistory(false);
if (lastCommand == null || !lastCommand.equals(command))
{
viewer.evalStringQuiet(command + "\n");
}
jmolHistory(true);
lastCommand = command;
}
Thread colourby = null;
/**
* Sends a set of colour commands to the structure viewer
*
* @param commands
*/
@Override
protected void colourBySequence(final String[] commands)
{
if (colourby != null)
{
colourby.interrupt();
colourby = null;
}
colourby = new Thread(new Runnable()
{
@Override
public void run()
{
for (String cmd : commands)
{
executeWhenReady(cmd);
}
}
});
colourby.start();
}
/**
* @param files
* @param viewPanel
* @return
*/
@Override
protected String[] getColourBySequenceCommands(
String[] files, AlignmentViewPanel viewPanel)
{
Map