package jalview.util;
import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
import jalview.api.FeatureRenderer;
import jalview.api.SequenceRenderer;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.HiddenColumns;
import jalview.datamodel.SequenceI;
import jalview.ext.rbvi.chimera.AtomSpecModel;
import jalview.renderer.seqfeatures.FeatureColourFinder;
import jalview.structure.StructureMapping;
import jalview.structures.models.AAStructureBindingModel;
import java.awt.Color;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A class with common methods for building commands for Jmol, Chimera, or other
*
* @author gmcarstairs
*/
public abstract class StructureCommands
{
/**
* Helper method to add one contiguous range to the AtomSpec model for the given
* value (creating the model if necessary). As used by Jalview, {@code value} is
*
* a colour, when building a 'colour structure by sequence' command
* a feature value, when building a 'set Chimera attributes from features'
* command
*
*
* @param map
* @param value
* @param model
* @param startPos
* @param endPos
* @param chain
*/
public static void addAtomSpecRange(Map map,
Object value,
int model, int startPos, int endPos, String chain)
{
/*
* Get/initialize map of data for the colour
*/
AtomSpecModel atomSpec = map.get(value);
if (atomSpec == null)
{
atomSpec = new AtomSpecModel();
map.put(value, atomSpec);
}
atomSpec.addRange(model, startPos, endPos, chain);
}
/**
* Build a data structure which records contiguous subsequences by model and
* chain. From this we can easily generate the Chimera or Jmol specific
* selection expression.
*
*
* Color
* Model number
* Chain
* list of start/end ranges
*
*
* Ordering is by order of addition (for colours and positions), natural
* ordering (for models and chains)
*
* @param viewPanel
* @return
*/
public static Map buildColoursMap(
AAStructureBindingModel binding, AlignmentViewPanel viewPanel)
{
FeatureRenderer fr = viewPanel.getFeatureRenderer();
FeatureColourFinder finder = new FeatureColourFinder(fr);
AlignViewportI viewport = viewPanel.getAlignViewport();
HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
AlignmentI al = viewport.getAlignment();
SequenceRenderer sr = binding.getSequenceRenderer(viewPanel);
String[] files = binding.getStructureFiles();
SequenceI[][] sequence = binding.getSequence();
Map colourMap = new LinkedHashMap<>();
Color lastColour = null;
for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
{
StructureMapping[] mapping = binding.getSsm()
.getMapping(files[pdbfnum]);
if (mapping == null || mapping.length < 1)
{
continue;
}
int startPos = -1, lastPos = -1;
String lastChain = "";
for (int s = 0; s < sequence[pdbfnum].length; s++)
{
for (int sp, m = 0; m < mapping.length; m++)
{
final SequenceI seq = sequence[pdbfnum][s];
if (mapping[m].getSequence() == seq
&& (sp = al.findIndex(seq)) > -1)
{
SequenceI asp = al.getSequenceAt(sp);
for (int r = 0; r < asp.getLength(); r++)
{
// no mapping to gaps in sequence
if (Comparison.isGap(asp.getCharAt(r)))
{
continue;
}
int pos = mapping[m].getPDBResNum(asp.findPosition(r));
if (pos < 1 || pos == lastPos)
{
continue;
}
Color colour = sr.getResidueColour(seq, r, finder);
/*
* hidden regions are shown gray or, optionally, ignored
*/
if (!cs.isVisible(r))
{
if (binding.isHideHiddenRegions())
{
continue;
}
else
{
colour = Color.GRAY;
}
}
final String chain = mapping[m].getChain();
/*
* Just keep incrementing the end position for this colour range
* _unless_ colour, PDB model or chain has changed, or there is a
* gap in the mapped residue sequence
*/
final boolean newColour = !colour.equals(lastColour);
final boolean nonContig = lastPos + 1 != pos;
final boolean newChain = !chain.equals(lastChain);
if (newColour || nonContig || newChain)
{
if (startPos != -1)
{
StructureCommands.addAtomSpecRange(colourMap, lastColour,
pdbfnum, startPos,
lastPos, lastChain);
}
startPos = pos;
}
lastColour = colour;
lastPos = pos;
lastChain = chain;
}
// final colour range
if (lastColour != null)
{
StructureCommands.addAtomSpecRange(colourMap, lastColour,
pdbfnum,
startPos, lastPos, lastChain);
}
}
}
}
}
return colourMap;
}
/**
* A helper method that takes a list of [start-end] ranges, format them as
* s1-e1,s2-e2 etc and appends to the string buffer. Ranges are sorted, and
* coalesced if they overlap. The chain token, if not null, is appended to each
* resulting subrange.
*
* @param sb
* @param rangeList
* @param chainToken
* @param firstPositionForModel
*/
protected static void appendResidueRange(StringBuilder sb, List rangeList,
String chainToken, boolean firstPositionForModel)
{
/*
* sort ranges into ascending start position order
*/
Collections.sort(rangeList, IntRangeComparator.ASCENDING);
int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
Iterator iterator = rangeList.iterator();
while (iterator.hasNext())
{
int[] range = iterator.next();
if (range[0] <= end + 1)
{
/*
* range overlaps or is contiguous with the last one
* - so just extend the end position, and carry on
* (unless this is the last in the list)
*/
end = Math.max(end, range[1]);
}
else
{
/*
* we have a break so append the last range
*/
appendRange(sb, start, end, chainToken, firstPositionForModel);
firstPositionForModel = false;
start = range[0];
end = range[1];
}
}
/*
* and append the last range
*/
if (!rangeList.isEmpty())
{
appendRange(sb, start, end, chainToken, firstPositionForModel);
}
}
/**
* A helper method that appends one start-end range, and an (optional) chain
* token to an atomspec (a null token is not appended)
*
* @param sb
* @param start
* @param end
* @param chainToken
* @param firstPositionForModel
*/
protected static void appendRange(StringBuilder sb, int start, int end,
String chainToken, boolean firstPositionForModel)
{
if (!firstPositionForModel)
{
sb.append(",");
}
if (end == start)
{
sb.append(start);
}
else
{
sb.append(start).append("-").append(end);
}
if (chainToken != null)
{
sb.append(chainToken);
}
}
}