+ /**
+ * Returns the FeatureRenderer for the given alignment view, or null if
+ * feature display is turned off in the view.
+ *
+ * @param avp
+ * @return
+ */
+ public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
+ {
+ AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
+ : avp;
+ return ap.getAlignViewport().isShowSequenceFeatures()
+ ? ap.getFeatureRenderer()
+ : null;
+ }
+
+ protected void setStructureCommands(StructureCommandsI cmd)
+ {
+ commandGenerator = cmd;
+ }
+
+ /**
+ * Records association of one chain id (formatted as "pdbid:chainCode") with
+ * the corresponding PDB file name
+ *
+ * @param chainId
+ * @param fileName
+ */
+ public void addChainFile(String chainId, String fileName)
+ {
+ chainFile.put(chainId, fileName);
+ }
+
+ /**
+ * Returns the PDB filename for the given chain id (formatted as
+ * "pdbid:chainCode"), or null if not found
+ *
+ * @param chainId
+ * @return
+ */
+ protected String getFileForChain(String chainId)
+ {
+ return chainFile.get(chainId);
+ }
+
+ @Override
+ public void updateColours(Object source)
+ {
+ AlignmentViewPanel ap = (AlignmentViewPanel) source;
+ // ignore events from panels not used to colour this view
+ if (!getViewer().isUsedForColourBy(ap))
+ {
+ return;
+ }
+ if (!isLoadingFromArchive())
+ {
+ colourBySequence(ap);
+ }
+ }
+
+ public StructureCommandsI getCommandGenerator()
+ {
+ return commandGenerator;
+ }
+
+ protected abstract ViewerType getViewerType();
+
+ /**
+ * Send a structure viewer command asynchronously in a new thread. If the
+ * progress message is not null, display this message while the command is
+ * executing.
+ *
+ * @param command
+ * @param progressMsg
+ */
+ protected void sendAsynchronousCommand(String command, String progressMsg)
+ {
+ final JalviewStructureDisplayI theViewer = getViewer();
+ final long handle = progressMsg == null ? 0
+ : theViewer.startProgressBar(progressMsg);
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ executeCommand(command, false);
+ } finally
+ {
+ if (progressMsg != null)
+ {
+ theViewer.stopProgressBar(null, handle);
+ }
+ }
+ }
+ });
+
+ }
+
+ /**
+ * <pre>
+ * Build a data structure which records residues for each colour.
+ * From this we can easily generate the viewer command for colour by sequence.
+ * Color
+ * Model number
+ * Chain
+ * Residue positions
+ * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
+ * </pre>
+ *
+ * @param ssm
+ * @param files
+ * @param sequence
+ * @param sr
+ * @param viewPanel
+ * @return
+ */
+ protected Map<Object, AtomSpecModel> buildColoursMap(
+ StructureSelectionManager ssm, String[] files,
+ SequenceI[][] sequence, SequenceRenderer sr, AlignmentViewPanel viewPanel)
+ {
+ FeatureRenderer fr = viewPanel.getFeatureRenderer();
+ FeatureColourFinder finder = new FeatureColourFinder(fr);
+ AlignViewportI viewport = viewPanel.getAlignViewport();
+ HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
+ AlignmentI al = viewport.getAlignment();
+ Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
+ Color lastColour = null;
+
+ for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+ {
+ final int modelNumber = pdbfnum + commandGenerator.getModelStartNo();
+ StructureMapping[] mapping = ssm.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);
+
+ /*
+ * darker colour for hidden regions
+ */
+ if (!cs.isVisible(r))
+ {
+ 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)
+ {
+ addAtomSpecRange(colourMap, lastColour, modelNumber,
+ startPos, lastPos, lastChain);
+ }
+ startPos = pos;
+ }
+ lastColour = colour;
+ lastPos = pos;
+ lastChain = chain;
+ }
+ // final colour range
+ if (lastColour != null)
+ {
+ addAtomSpecRange(colourMap, lastColour, modelNumber, startPos,
+ lastPos, lastChain);
+ }
+ // break;
+ }
+ }
+ }
+ }
+ return colourMap;
+ }
+
+ /**
+ * 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
+ * <ul>
+ * <li>a colour, when building a 'colour structure by sequence' command</li>
+ * <li>a feature value, when building a 'set Chimera attributes from features'
+ * command</li>
+ * </ul>
+ *
+ * @param map
+ * @param value
+ * @param model
+ * @param startPos
+ * @param endPos
+ * @param chain
+ */
+ public static final void addAtomSpecRange(Map<Object, AtomSpecModel> 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);
+ }