2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.structures.models;
23 import java.awt.Color;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.BitSet;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Locale;
36 import javax.swing.SwingUtilities;
38 import jalview.api.AlignViewportI;
39 import jalview.api.AlignmentViewPanel;
40 import jalview.api.FeatureRenderer;
41 import jalview.api.SequenceRenderer;
42 import jalview.api.StructureSelectionManagerProvider;
43 import jalview.api.structures.JalviewStructureDisplayI;
44 import jalview.bin.Console;
45 import jalview.datamodel.AlignmentI;
46 import jalview.datamodel.ColumnSelection;
47 import jalview.datamodel.HiddenColumns;
48 import jalview.datamodel.MappedFeatures;
49 import jalview.datamodel.PDBEntry;
50 import jalview.datamodel.SequenceFeature;
51 import jalview.datamodel.SequenceI;
52 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
53 import jalview.gui.AlignmentPanel;
54 import jalview.gui.Desktop;
55 import jalview.gui.StructureViewer.ViewerType;
56 import jalview.io.DataSourceType;
57 import jalview.io.StructureFile;
58 import jalview.renderer.seqfeatures.FeatureColourFinder;
59 import jalview.schemes.ColourSchemeI;
60 import jalview.schemes.ResidueProperties;
61 import jalview.structure.AtomSpec;
62 import jalview.structure.AtomSpecModel;
63 import jalview.structure.StructureCommandI;
64 import jalview.structure.StructureCommandsI;
65 import jalview.structure.StructureCommandsI.AtomSpecType;
66 import jalview.structure.StructureListener;
67 import jalview.structure.StructureMapping;
68 import jalview.structure.StructureSelectionManager;
69 import jalview.util.Comparison;
70 import jalview.util.MessageManager;
74 * A base class to hold common function for 3D structure model binding. Initial
75 * version created by refactoring JMol and Chimera binding models, but other
76 * structure viewers could in principle be accommodated in future.
81 public abstract class AAStructureBindingModel
82 extends SequenceStructureBindingModel
83 implements StructureListener, StructureSelectionManagerProvider
86 * Data bean class to simplify parameterisation in superposeStructures
88 public static class SuperposeData
90 public String filename;
94 public String chain = "";
97 * is the mapped sequence not protein ?
102 * The pdb residue number (if any) mapped to columns of the alignment
104 public int[] pdbResNo; // or use SparseIntArray?
106 public String modelId;
112 * width of alignment (number of columns that may potentially
113 * participate in superposition)
115 * structure viewer model number
117 public SuperposeData(int width, String model)
119 pdbResNo = new int[width];
124 private static final int MIN_POS_TO_SUPERPOSE = 4;
126 private static final String COLOURING_STRUCTURES = MessageManager
127 .getString("status.colouring_structures");
130 * the Jalview panel through which the user interacts
131 * with the structure viewer
133 private JalviewStructureDisplayI viewer;
136 * helper that generates command syntax
138 private StructureCommandsI commandGenerator;
140 private StructureSelectionManager ssm;
143 * modelled chains, formatted as "pdbid:chainCode"
145 private List<String> chainNames;
148 * lookup of pdb file name by key "pdbid:chainCode"
150 private Map<String, String> chainFile;
153 * distinct PDB entries (pdb files) associated
156 private PDBEntry[] pdbEntry;
159 * sequences mapped to each pdbentry
161 private SequenceI[][] sequence;
164 * array of target chains for sequences - tied to pdbentry and sequence[]
166 private String[][] chains;
169 * datasource protocol for access to PDBEntrylatest
171 DataSourceType protocol = null;
173 protected boolean colourBySequence = true;
176 * true if all sequences appear to be nucleotide
178 private boolean nucleotide;
180 private boolean finishedInit = false;
183 * current set of model filenames loaded in the viewer
185 protected String[] modelFileNames = null;
187 public String fileLoadingError;
189 protected Thread externalViewerMonitor;
197 public AAStructureBindingModel(StructureSelectionManager ssm,
201 this.sequence = seqs;
202 chainNames = new ArrayList<>();
203 chainFile = new HashMap<>();
214 public AAStructureBindingModel(StructureSelectionManager ssm,
215 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
216 DataSourceType protocol)
218 this(ssm, sequenceIs);
219 this.nucleotide = Comparison.isNucleotide(sequenceIs);
220 this.pdbEntry = pdbentry;
221 this.protocol = protocol;
225 private boolean resolveChains()
228 * final count of chain mappings discovered
231 // JBPNote: JAL-2693 - this should be a list of chain mappings per
232 // [pdbentry][sequence]
233 String[][] newchains = new String[pdbEntry.length][];
235 for (PDBEntry pdb : pdbEntry)
237 SequenceI[] seqsForPdb = sequence[pe];
238 if (seqsForPdb != null)
240 newchains[pe] = new String[seqsForPdb.length];
242 for (SequenceI asq : seqsForPdb)
244 String chain = (chains != null && chains[pe] != null)
247 SequenceI sq = (asq.getDatasetSequence() == null) ? asq
248 : asq.getDatasetSequence();
249 if (sq.getAllPDBEntries() != null)
251 for (PDBEntry pdbentry : sq.getAllPDBEntries())
253 if (pdb.getFile() != null && pdbentry.getFile() != null
254 && pdb.getFile().equals(pdbentry.getFile()))
256 String chaincode = pdbentry.getChainCode();
257 if (chaincode != null && chaincode.length() > 0)
266 newchains[pe][se] = chain;
274 return chainmaps > 0;
277 public StructureSelectionManager getSsm()
283 * Returns the i'th PDBEntry (or null)
288 public PDBEntry getPdbEntry(int i)
290 return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
294 * Answers true if this binding includes the given PDB id, else false
299 public boolean hasPdbId(String pdbId)
301 if (pdbEntry != null)
303 for (PDBEntry pdb : pdbEntry)
305 if (pdb.getId().equals(pdbId))
315 * Returns the number of modelled PDB file entries.
319 public int getPdbCount()
321 return pdbEntry == null ? 0 : pdbEntry.length;
324 public SequenceI[][] getSequence()
329 public String[][] getChains()
334 public DataSourceType getProtocol()
339 // TODO may remove this if calling methods can be pulled up here
340 protected void setPdbentry(PDBEntry[] pdbentry)
342 this.pdbEntry = pdbentry;
345 protected void setSequence(SequenceI[][] sequence)
347 this.sequence = sequence;
350 protected void setChains(String[][] chains)
352 this.chains = chains;
356 * Construct a title string for the viewer window based on the data Jalview
365 public String getViewerTitle(String viewerName, boolean verbose)
367 if (getSequence() == null || getSequence().length < 1
368 || getPdbCount() < 1 || getSequence()[0].length < 1)
370 return ("Jalview " + viewerName + " Window");
372 // TODO: give a more informative title when multiple structures are
374 StringBuilder title = new StringBuilder(64);
375 final PDBEntry pdbe = getPdbEntry(0);
376 title.append(viewerName + " view for " + getSequence()[0][0].getName()
377 + ":" + pdbe.getId());
381 String method = (String) pdbe.getProperty("method");
384 title.append(" Method: ").append(method);
386 String chain = (String) pdbe.getProperty("chains");
389 title.append(" Chain:").append(chain);
392 return title.toString();
396 * Called by after closeViewer is called, to release any resources and
397 * references so they can be garbage collected. Override if needed.
399 protected void releaseUIResources()
404 public void releaseReferences(Object svl)
408 public boolean isColourBySequence()
410 return colourBySequence;
414 * Called when the binding thinks the UI needs to be refreshed after a
415 * structure viewer state change. This could be because structures were
416 * loaded, or because an error has occurred. Default does nothing, override as
419 public void refreshGUI()
424 * Instruct the Jalview binding to update the pdbentries vector if necessary
425 * prior to matching the jmol view's contents to the list of structure files
426 * Jalview knows about. By default does nothing, override as required.
428 public void refreshPdbEntries()
432 public void setColourBySequence(boolean colourBySequence)
434 this.colourBySequence = colourBySequence;
437 protected void addSequenceAndChain(int pe, SequenceI[] seq,
440 if (pe < 0 || pe >= getPdbCount())
442 throw new Error(MessageManager.formatMessage(
443 "error.implementation_error_no_pdbentry_from_index",
445 { Integer.valueOf(pe).toString() }));
447 final String nullChain = "TheNullChain";
448 List<SequenceI> s = new ArrayList<>();
449 List<String> c = new ArrayList<>();
450 if (getChains() == null)
452 setChains(new String[getPdbCount()][]);
454 if (getSequence()[pe] != null)
456 for (int i = 0; i < getSequence()[pe].length; i++)
458 s.add(getSequence()[pe][i]);
459 if (getChains()[pe] != null)
461 if (i < getChains()[pe].length)
463 c.add(getChains()[pe][i]);
472 if (tchain != null && tchain.length > 0)
479 for (int i = 0; i < seq.length; i++)
481 if (!s.contains(seq[i]))
484 if (tchain != null && i < tchain.length)
486 c.add(tchain[i] == null ? nullChain : tchain[i]);
490 SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
491 getSequence()[pe] = tmp;
494 String[] tch = c.toArray(new String[c.size()]);
495 for (int i = 0; i < tch.length; i++)
497 if (tch[i] == nullChain)
502 getChains()[pe] = tch;
506 getChains()[pe] = null;
511 * add structures and any known sequence associations
513 * @returns the pdb entries added to the current set.
515 public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
516 SequenceI[][] seq, String[][] chns)
518 List<PDBEntry> v = new ArrayList<>();
519 List<int[]> rtn = new ArrayList<>();
520 for (int i = 0; i < getPdbCount(); i++)
522 v.add(getPdbEntry(i));
524 for (int i = 0; i < pdbe.length; i++)
526 int r = v.indexOf(pdbe[i]);
527 if (r == -1 || r >= getPdbCount())
529 rtn.add(new int[] { v.size(), i });
534 // just make sure the sequence/chain entries are all up to date
535 addSequenceAndChain(r, seq[i], chns[i]);
538 pdbe = v.toArray(new PDBEntry[v.size()]);
542 // expand the tied sequence[] and string[] arrays
543 SequenceI[][] sqs = new SequenceI[getPdbCount()][];
544 String[][] sch = new String[getPdbCount()][];
545 System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
546 System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
549 pdbe = new PDBEntry[rtn.size()];
550 for (int r = 0; r < pdbe.length; r++)
552 int[] stri = (rtn.get(r));
553 // record the pdb file as a new addition
554 pdbe[r] = getPdbEntry(stri[0]);
555 // and add the new sequence/chain entries
556 addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
567 * Add sequences to the pe'th pdbentry's sequence set.
572 public void addSequence(int pe, SequenceI[] seq)
574 addSequenceAndChain(pe, seq, null);
578 * add the given sequences to the mapping scope for the given pdb file handle
581 * - pdbFile identifier
583 * - set of sequences it can be mapped to
585 public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
587 for (int pe = 0; pe < getPdbCount(); pe++)
589 if (getPdbEntry(pe).getFile().equals(pdbFile))
591 addSequence(pe, seq);
597 public abstract void highlightAtoms(List<AtomSpec> atoms);
599 protected boolean isNucleotide()
601 return this.nucleotide;
605 * Returns a readable description of all mappings for the wrapped pdbfile to
606 * any mapped sequences
612 public String printMappings()
614 if (pdbEntry == null)
618 StringBuilder sb = new StringBuilder(128);
619 for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
621 String pdbfile = getPdbEntry(pdbe).getFile();
622 List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
623 sb.append(getSsm().printMappings(pdbfile, seqs));
625 return sb.toString();
629 * Returns the mapped structure position for a given aligned column of a given
630 * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
631 * not mapped to structure.
638 protected int getMappedPosition(SequenceI seq, int alignedPos,
639 StructureMapping mapping)
641 if (alignedPos >= seq.getLength())
646 if (Comparison.isGap(seq.getCharAt(alignedPos)))
650 int seqPos = seq.findPosition(alignedPos);
651 int pos = mapping.getPDBResNum(seqPos);
656 * Helper method to identify residues that can participate in a structure
657 * superposition command. For each structure, identify a sequence in the
658 * alignment which is mapped to the structure. Identify non-gapped columns in
659 * the sequence which have a mapping to a residue in the structure. Returns
660 * the index of the first structure that has a mapping to the alignment.
663 * the sequence alignment which is the basis of structure
666 * a BitSet, where bit j is set to indicate that every structure has
667 * a mapped residue present in column j (so the column can
668 * participate in structure alignment)
670 * an array of data beans corresponding to pdb file index
673 protected int findSuperposableResidues(AlignmentI alignment,
675 AAStructureBindingModel.SuperposeData[] structures)
677 int refStructure = -1;
678 String[] files = getStructureFiles();
683 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
685 StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
689 * Find the first mapped sequence (if any) for this PDB entry which is in
692 final int seqCountForPdbFile = getSequence()[pdbfnum].length;
693 for (int s = 0; s < seqCountForPdbFile; s++)
695 for (StructureMapping mapping : mappings)
697 final SequenceI theSequence = getSequence()[pdbfnum][s];
698 if (mapping.getSequence() == theSequence
699 && alignment.findIndex(theSequence) > -1)
701 if (refStructure < 0)
703 refStructure = pdbfnum;
705 for (int r = 0; r < alignment.getWidth(); r++)
711 int pos = getMappedPosition(theSequence, r, mapping);
712 if (pos < 1 || pos == lastPos)
718 structures[pdbfnum].pdbResNo[r] = pos;
720 String chain = mapping.getChain();
721 if (chain != null && chain.trim().length() > 0)
723 structures[pdbfnum].chain = chain;
725 structures[pdbfnum].pdbId = mapping.getPdbId();
726 structures[pdbfnum].isRna = !theSequence.isProtein();
729 * move on to next pdb file (ignore sequences for other chains
730 * for the same structure)
732 s = seqCountForPdbFile;
733 break; // fixme break out of two loops here!
742 * Returns true if the structure viewer has loaded all of the files of
743 * interest (identified by the file mapping having been set up), or false if
744 * any are still not loaded after a timeout interval.
748 protected boolean waitForFileLoad(String[] files)
751 * give up after 10 secs plus 1 sec per file
753 long starttime = System.currentTimeMillis();
754 long endTime = 10000 + 1000 * files.length + starttime;
755 String notLoaded = null;
757 boolean waiting = true;
758 while (waiting && System.currentTimeMillis() < endTime)
761 for (String file : files)
770 StructureMapping[] sm = getSsm().getMapping(file);
771 if (sm == null || sm.length == 0)
775 } catch (Throwable x)
784 jalview.bin.Console.errPrintln(
785 "Timed out waiting for structure viewer to load file "
793 public boolean isListeningFor(SequenceI seq)
795 if (sequence != null)
797 for (SequenceI[] seqs : sequence)
801 for (SequenceI s : seqs)
803 if (s == seq || (s.getDatasetSequence() != null
804 && s.getDatasetSequence() == seq.getDatasetSequence()))
815 public boolean isFinishedInit()
820 public void setFinishedInit(boolean fi)
822 this.finishedInit = fi;
826 * Returns a list of chains mapped in this viewer, formatted as
831 public List<String> getChainNames()
837 * Returns the Jalview panel hosting the structure viewer (if any)
841 public JalviewStructureDisplayI getViewer()
846 public void setViewer(JalviewStructureDisplayI v)
852 * Constructs and sends a command to align structures against a reference
853 * structure, based on one or more sequence alignments. May optionally return
854 * an error or warning message for the alignment command(s).
857 * an array of one or more alignment views to process
860 public String superposeStructures(List<AlignmentViewPanel> alignWith)
863 String[] files = getStructureFiles();
865 if (!waitForFileLoad(files))
871 for (AlignmentViewPanel view : alignWith)
873 AlignmentI alignment = view.getAlignment();
874 HiddenColumns hiddenCols = alignment.getHiddenColumns();
876 * 'matched' bit i will be set for visible alignment columns i where
877 * all sequences have a residue with a mapping to their PDB structure
879 final int width = alignment.getWidth();
880 BitSet matched = new BitSet();
881 ColumnSelection cs = view.getAlignViewport().getColumnSelection();
882 // restrict to active column selection, if there is one
883 if (cs != null && cs.hasSelectedColumns()
884 && cs.getSelected().size() >= 4)
886 for (int s : cs.getSelected())
893 for (int m = 0; m < width; m++)
895 if (hiddenCols == null || hiddenCols.isVisible(m))
901 AAStructureBindingModel.SuperposeData[] structures = new AAStructureBindingModel.SuperposeData[files.length];
902 for (int f = 0; f < files.length; f++)
904 structures[f] = new AAStructureBindingModel.SuperposeData(width,
905 getModelIdForFile(files[f]));
909 * Calculate the superposable alignment columns ('matched'), and the
910 * corresponding structure residue positions (structures.pdbResNo)
912 int refStructure = findSuperposableResidues(alignment, matched,
916 * require at least 4 positions to be able to execute superposition
918 int nmatched = matched.cardinality();
919 if (nmatched < MIN_POS_TO_SUPERPOSE)
921 String msg = MessageManager
922 .formatMessage("label.insufficient_residues", nmatched);
923 error += view.getViewName() + ": " + msg + "; ";
928 * get a model of the superposable residues in the reference structure
930 AtomSpecModel refAtoms = getAtomSpec(structures[refStructure],
934 * Show all as backbone before doing superposition(s)
935 * (residues used for matching will be shown as ribbon)
937 // todo better way to ensure synchronous than setting getReply true!!
938 executeCommands(commandGenerator.showBackbone(), true, null);
940 AtomSpecType backbone = structures[refStructure].isRna
941 ? AtomSpecType.PHOSPHATE
942 : AtomSpecType.ALPHA;
943 List<AtomSpecModel> models = new ArrayList<AtomSpecModel>();
944 models.add(refAtoms);
946 * superpose each (other) structure to the reference in turn
948 for (int i = 0; i < structures.length; i++)
950 if (i != refStructure)
952 AtomSpecModel atomSpec = getAtomSpec(structures[i], matched);
953 List<StructureCommandI> commands = commandGenerator
954 .superposeStructures(refAtoms, atomSpec, backbone);
955 List<String> replies = executeCommands(commands, true, null);
956 for (String reply : replies)
958 // return this error (Chimera only) to the user
959 if (reply.toLowerCase(Locale.ROOT)
960 .contains("unequal numbers of atoms"))
962 error += "; " + reply;
965 models.add(atomSpec);
968 List<StructureCommandI> finalView = commandGenerator
969 .centerViewOn(models);
970 if (finalView != null && finalView.size() > 0)
972 executeCommands(finalView, false, "Centered on Superposition");
978 private AtomSpecModel getAtomSpec(
979 AAStructureBindingModel.SuperposeData superposeData,
982 AtomSpecModel model = new AtomSpecModel();
983 int nextColumnMatch = matched.nextSetBit(0);
984 while (nextColumnMatch != -1)
986 int pdbResNum = superposeData.pdbResNo[nextColumnMatch];
987 model.addRange(superposeData.modelId, pdbResNum, pdbResNum,
988 superposeData.chain);
989 nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
996 * returns the current sequenceRenderer that should be used to colour the
1003 public abstract SequenceRenderer getSequenceRenderer(
1004 AlignmentViewPanel alignment);
1007 * Sends a command to the structure viewer to colour each chain with a
1008 * distinct colour (to the extent supported by the viewer)
1010 public void colourByChain()
1012 colourBySequence = false;
1014 // TODO: JAL-628 colour chains distinctly across all visible models
1016 executeCommand(false, COLOURING_STRUCTURES,
1017 commandGenerator.colourByChain());
1021 * Sends a command to the structure viewer to colour each chain with a
1022 * distinct colour (to the extent supported by the viewer)
1024 public void colourByCharge()
1026 colourBySequence = false;
1028 executeCommands(commandGenerator.colourByCharge(), false,
1029 COLOURING_STRUCTURES);
1033 * Sends a command to the structure to apply a colour scheme (defined in
1034 * Jalview but not necessarily applied to the alignment), which defines a
1035 * colour per residue letter. More complex schemes (e.g. that depend on
1036 * consensus) cannot be used here and are ignored.
1040 public void colourByJalviewColourScheme(ColourSchemeI cs)
1042 colourBySequence = false;
1044 if (cs == null || !cs.isSimple())
1050 * build a map of {Residue3LetterCode, Color}
1052 Map<String, Color> colours = new HashMap<>();
1053 List<String> residues = ResidueProperties.getResidues(isNucleotide(),
1055 for (String resName : residues)
1057 char res = resName.length() == 3
1058 ? ResidueProperties.getSingleCharacterCode(resName)
1059 : resName.charAt(0);
1060 Color colour = cs.findColour(res, 0, null, null, 0f);
1061 colours.put(resName, colour);
1065 * pass to the command constructor, and send the command
1067 List<StructureCommandI> cmd = commandGenerator
1068 .colourByResidues(colours);
1069 executeCommands(cmd, false, COLOURING_STRUCTURES);
1072 public void setBackgroundColour(Color col)
1074 StructureCommandI cmd = commandGenerator.setBackgroundColour(col);
1075 executeCommand(false, null, cmd);
1079 * Execute one structure viewer command. If {@code getReply} is true, may
1080 * optionally return one or more reply messages, else returns null.
1085 protected abstract List<String> executeCommand(StructureCommandI cmd,
1089 * Executes one or more structure viewer commands
1095 public List<String> executeCommands(List<StructureCommandI> commands,
1096 boolean getReply, String msg)
1098 return executeCommand(getReply, msg,
1099 commands.toArray(new StructureCommandI[commands.size()]));
1103 * Executes one or more structure viewer commands, optionally returning the
1104 * reply, and optionally showing a status message while the command is being
1107 * If a reply is wanted, the execution is done synchronously (waits),
1108 * otherwise it is done in a separate thread (doesn't wait). WARNING: if you
1109 * are sending commands that need to execute before later calls to
1110 * executeCommand (e.g. mouseovers, which clean up after previous ones) then
1111 * set getReply true to ensure that commands are not executed out of order.
1118 protected List<String> executeCommand(boolean getReply, String msg,
1119 StructureCommandI... cmds)
1121 JalviewStructureDisplayI theViewer = getViewer();
1122 final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
1127 * execute and wait for reply
1129 List<String> response = new ArrayList<>();
1132 for (StructureCommandI cmd : cmds)
1134 List<String> replies = executeCommand(cmd, true);
1135 if (replies != null)
1137 response.addAll(replies);
1145 theViewer.stopProgressBar(null, handle);
1153 String threadName = msg == null ? "StructureCommand" : msg;
1154 new Thread(new Runnable()
1161 for (StructureCommandI cmd : cmds)
1163 executeCommand(cmd, false);
1169 SwingUtilities.invokeLater(new Runnable()
1174 theViewer.stopProgressBar(null, handle);
1180 }, threadName).start();
1185 * Colours any structures associated with sequences in the given alignment as
1186 * coloured in the alignment view, provided colourBySequence is enabled
1188 public void colourBySequence(AlignmentViewPanel alignmentv)
1190 if (!colourBySequence || !isLoadingFinished() || getSsm() == null)
1194 Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, sequence,
1197 List<StructureCommandI> colourBySequenceCommands = commandGenerator
1198 .colourBySequence(colourMap);
1199 executeCommands(colourBySequenceCommands, false, COLOURING_STRUCTURES);
1203 * Centre the display in the structure viewer
1205 public void focusView()
1207 executeCommand(false, null, commandGenerator.focusView());
1211 * Generates and executes a command to show only specified chains in the
1212 * structure viewer. The list of chains to show should contain entries
1213 * formatted as "pdbid:chaincode".
1217 public void showChains(List<String> toShow)
1219 // todo or reformat toShow list entries as modelNo:pdbId:chainCode ?
1222 * Reformat the pdbid:chainCode values as modelNo:chainCode
1223 * since this is what is needed to construct the viewer command
1224 * todo: find a less messy way to do this
1226 List<String> showThese = new ArrayList<>();
1227 for (String chainId : toShow)
1229 String[] tokens = chainId.split("\\:");
1230 if (tokens.length == 2)
1232 String pdbFile = getFileForChain(chainId);
1233 String model = getModelIdForFile(pdbFile);
1234 showThese.add(model + ":" + tokens[1]);
1237 executeCommands(commandGenerator.showChains(showThese), false, null);
1241 * Answers the structure viewer's model id given a PDB file name. Returns an
1242 * empty string if model id is not found.
1247 protected abstract String getModelIdForFile(String chainId);
1249 public boolean hasFileLoadingError()
1251 return fileLoadingError != null && fileLoadingError.length() > 0;
1255 * Returns the FeatureRenderer for the given alignment view
1260 public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
1262 AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
1268 return ap.getFeatureRenderer();
1271 protected void setStructureCommands(StructureCommandsI cmd)
1273 commandGenerator = cmd;
1277 * Records association of one chain id (formatted as "pdbid:chainCode") with
1278 * the corresponding PDB file name
1283 public void addChainFile(String chainId, String fileName)
1285 chainFile.put(chainId, fileName);
1289 * Returns the PDB filename for the given chain id (formatted as
1290 * "pdbid:chainCode"), or null if not found
1295 protected String getFileForChain(String chainId)
1297 return chainFile.get(chainId);
1301 public void updateColours(Object source)
1303 if (getViewer() == null)
1305 // can happen if a viewer was not instantiated or cleaned up and is still
1306 // registered - mostly during tests
1309 AlignmentViewPanel ap = (AlignmentViewPanel) source;
1310 // ignore events from panels not used to colour this view
1311 if (!getViewer().isUsedForColourBy(ap))
1315 if (!isLoadingFromArchive())
1317 colourBySequence(ap);
1321 public StructureCommandsI getCommandGenerator()
1323 return commandGenerator;
1326 protected abstract ViewerType getViewerType();
1329 * Builds a data structure which records mapped structure residues for each
1330 * colour. From this we can easily generate the viewer commands for colour by
1331 * sequence. Constructs and returns a map of {@code Color} to
1332 * {@code AtomSpecModel}, where the atomspec model holds
1340 * Ordering is by order of addition (for colours), natural ordering (for
1341 * models and chains)
1348 protected Map<Object, AtomSpecModel> buildColoursMap(
1349 StructureSelectionManager ssm, SequenceI[][] sequence,
1350 AlignmentViewPanel viewPanel)
1352 String[] files = getStructureFiles();
1353 SequenceRenderer sr = getSequenceRenderer(viewPanel);
1354 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1355 FeatureColourFinder finder = new FeatureColourFinder(fr);
1356 AlignViewportI viewport = viewPanel.getAlignViewport();
1357 HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
1358 AlignmentI al = viewport.getAlignment();
1359 Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
1360 Color lastColour = null;
1362 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1364 final String modelId = getModelIdForFile(files[pdbfnum]);
1365 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1367 if (mapping == null || mapping.length < 1)
1372 int startPos = -1, lastPos = -1;
1373 String lastChain = "";
1374 for (int s = 0; s < sequence[pdbfnum].length; s++)
1376 for (int sp, m = 0; m < mapping.length; m++)
1378 final SequenceI seq = sequence[pdbfnum][s];
1379 if (mapping[m].getSequence() == seq
1380 && (sp = al.findIndex(seq)) > -1)
1382 SequenceI asp = al.getSequenceAt(sp);
1383 for (int r = 0; r < asp.getLength(); r++)
1385 // no mapping to gaps in sequence
1386 if (Comparison.isGap(asp.getCharAt(r)))
1390 int pos = mapping[m].getPDBResNum(asp.findPosition(r));
1392 if (pos < 1 || pos == lastPos)
1397 Color colour = sr.getResidueColour(seq, r, finder);
1400 * darker colour for hidden regions
1402 if (!cs.isVisible(r))
1404 colour = Color.GRAY;
1407 final String chain = mapping[m].getChain();
1410 * Just keep incrementing the end position for this colour range
1411 * _unless_ colour, PDB model or chain has changed, or there is a
1412 * gap in the mapped residue sequence
1414 final boolean newColour = !colour.equals(lastColour);
1415 final boolean nonContig = lastPos + 1 != pos;
1416 final boolean newChain = !chain.equals(lastChain);
1417 if (newColour || nonContig || newChain)
1421 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1422 lastPos, lastChain);
1426 lastColour = colour;
1430 // final colour range
1431 if (lastColour != null)
1433 addAtomSpecRange(colourMap, lastColour, modelId, startPos,
1434 lastPos, lastChain);
1445 * todo better refactoring (map lookup or similar to get viewer structure id)
1451 protected String getModelId(int pdbfnum, String file)
1453 return String.valueOf(pdbfnum);
1457 * Saves chains, formatted as "pdbId:chainCode", and lookups from this to the
1458 * full PDB file path
1463 public void stashFoundChains(StructureFile pdb, String file)
1465 for (int i = 0; i < pdb.getChains().size(); i++)
1467 String chid = pdb.getId() + ":" + pdb.getChains().elementAt(i).id;
1468 addChainFile(chid, file);
1469 getChainNames().add(chid);
1474 * Helper method to add one contiguous range to the AtomSpec model for the
1475 * given value (creating the model if necessary). As used by Jalview,
1478 * <li>a colour, when building a 'colour structure by sequence' command</li>
1479 * <li>a feature value, when building a 'set Chimera attributes from features'
1490 public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
1491 Object value, String model, int startPos, int endPos,
1495 * Get/initialize map of data for the colour
1497 AtomSpecModel atomSpec = map.get(value);
1498 if (atomSpec == null)
1500 atomSpec = new AtomSpecModel();
1501 map.put(value, atomSpec);
1504 atomSpec.addRange(model, startPos, endPos, chain);
1508 * Returns the file extension (including '.' separator) to use for a saved
1509 * viewer session file. Default is to return null (not supported), override as
1514 public String getSessionFileExtension()
1520 * If supported, saves the state of the structure viewer to a temporary file
1521 * and returns the file. Returns null and logs an error on any failure.
1525 public File saveSession()
1527 String prefix = getViewerType().toString();
1528 String suffix = getSessionFileExtension();
1532 f = File.createTempFile(prefix, suffix);
1534 } catch (IOException e)
1536 Console.error(String.format("Error saving %s session: %s", prefix,
1544 * Use restoreSession when you want to restore a previously saved sesssion to
1545 * the running viewer instance.
1547 * @param absolutePath
1549 public void restoreSession(String absolutePath)
1551 String prefix = getViewerType().toString();
1555 StructureCommandI cmd = commandGenerator.restoreSession(absolutePath);
1558 executeCommand(cmd, false);
1560 } catch (Throwable e)
1562 Console.error(String.format("Error restoring %s session: %s", prefix,
1569 * Saves the structure viewer session to the given file
1573 protected void saveSession(File f)
1575 StructureCommandI cmd = commandGenerator.saveSession(f.getPath());
1578 executeCommand(cmd, false);
1583 * Returns true if the viewer is an external structure viewer for which the
1584 * process is still alive, else false (for Jmol, or an external viewer which
1585 * the user has independently closed)
1589 public boolean isViewerRunning()
1595 * Closes Jalview's structure viewer panel and releases associated resources.
1596 * If it is managing an external viewer program, and {@code forceClose} is
1597 * true, also asks that program to close.
1601 public void closeViewer(boolean forceClose)
1603 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
1604 releaseUIResources();
1607 * end the thread that closes this panel if the external viewer closes
1609 if (externalViewerMonitor != null)
1611 externalViewerMonitor.interrupt();
1612 externalViewerMonitor = null;
1619 StructureCommandI cmd = getCommandGenerator().closeViewer();
1622 executeCommand(cmd, false);
1628 * Returns the URL of a help page for the structure viewer, or null if none is
1633 public String getHelpURL()
1640 * Helper method to build a map of
1641 * { featureType, { feature value, AtomSpecModel } }
1647 protected Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
1648 AlignmentViewPanel viewPanel)
1650 Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
1651 String[] files = getStructureFiles();
1657 FeatureRenderer fr = viewPanel.getFeatureRenderer();
1663 AlignViewportI viewport = viewPanel.getAlignViewport();
1664 List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
1667 * if alignment is showing features from complement, we also transfer
1668 * these features to the corresponding mapped structure residues
1670 boolean showLinkedFeatures = viewport.isShowComplementFeatures();
1671 List<String> complementFeatures = new ArrayList<>();
1672 FeatureRenderer complementRenderer = null;
1673 if (showLinkedFeatures)
1675 AlignViewportI comp = fr.getViewport().getCodingComplement();
1678 complementRenderer = Desktop.getAlignFrameFor(comp)
1679 .getFeatureRenderer();
1680 complementFeatures = complementRenderer.getDisplayedFeatureTypes();
1683 if (visibleFeatures.isEmpty() && complementFeatures.isEmpty())
1688 AlignmentI alignment = viewPanel.getAlignment();
1689 SequenceI[][] seqs = getSequence();
1691 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
1693 String modelId = getModelIdForFile(files[pdbfnum]);
1694 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
1696 if (mapping == null || mapping.length < 1)
1701 for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
1703 for (int m = 0; m < mapping.length; m++)
1705 final SequenceI seq = seqs[pdbfnum][seqNo];
1706 int sp = alignment.findIndex(seq);
1707 StructureMapping structureMapping = mapping[m];
1708 if (structureMapping.getSequence() == seq && sp > -1)
1711 * found a sequence with a mapping to a structure;
1712 * now scan its features
1714 if (!visibleFeatures.isEmpty())
1716 scanSequenceFeatures(visibleFeatures, structureMapping, seq,
1719 if (showLinkedFeatures)
1721 scanComplementFeatures(complementRenderer, structureMapping,
1722 seq, theMap, modelId);
1732 * Ask the structure viewer to open a session file. Returns true if
1733 * successful, else false (or not supported).
1738 public boolean openSession(String filepath)
1740 StructureCommandI cmd = getCommandGenerator().openSession(filepath);
1745 executeCommand(cmd, true);
1746 // todo: test for failure - how?
1751 * Scans visible features in mapped positions of the CDS/peptide complement,
1752 * and adds any found to the map of attribute values/structure positions
1754 * @param complementRenderer
1755 * @param structureMapping
1758 * @param modelNumber
1760 protected static void scanComplementFeatures(
1761 FeatureRenderer complementRenderer,
1762 StructureMapping structureMapping, SequenceI seq,
1763 Map<String, Map<Object, AtomSpecModel>> theMap,
1767 * for each sequence residue mapped to a structure position...
1769 for (int seqPos : structureMapping.getMapping().keySet())
1772 * find visible complementary features at mapped position(s)
1774 MappedFeatures mf = complementRenderer
1775 .findComplementFeaturesAtResidue(seq, seqPos);
1778 for (SequenceFeature sf : mf.features)
1780 String type = sf.getType();
1783 * Don't copy features which originated from Chimera
1785 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1786 .equals(sf.getFeatureGroup()))
1792 * record feature 'value' (score/description/type) as at the
1793 * corresponding structure position
1795 List<int[]> mappedRanges = structureMapping
1796 .getPDBResNumRanges(seqPos, seqPos);
1798 if (!mappedRanges.isEmpty())
1800 String value = sf.getDescription();
1801 if (value == null || value.length() == 0)
1805 float score = sf.getScore();
1806 if (score != 0f && !Float.isNaN(score))
1808 value = Float.toString(score);
1810 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1811 if (featureValues == null)
1813 featureValues = new HashMap<>();
1814 theMap.put(type, featureValues);
1816 for (int[] range : mappedRanges)
1818 addAtomSpecRange(featureValues, value, modelNumber, range[0],
1819 range[1], structureMapping.getChain());
1828 * Inspect features on the sequence; for each feature that is visible,
1829 * determine its mapped ranges in the structure (if any) according to the
1830 * given mapping, and add them to the map.
1832 * @param visibleFeatures
1838 protected static void scanSequenceFeatures(List<String> visibleFeatures,
1839 StructureMapping mapping, SequenceI seq,
1840 Map<String, Map<Object, AtomSpecModel>> theMap, String modelId)
1842 List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
1843 visibleFeatures.toArray(new String[visibleFeatures.size()]));
1844 for (SequenceFeature sf : sfs)
1846 String type = sf.getType();
1849 * Don't copy features which originated from Chimera
1851 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
1852 .equals(sf.getFeatureGroup()))
1857 List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
1860 if (!mappedRanges.isEmpty())
1862 String value = sf.getDescription();
1863 if (value == null || value.length() == 0)
1867 float score = sf.getScore();
1868 if (score != 0f && !Float.isNaN(score))
1870 value = Float.toString(score);
1872 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
1873 if (featureValues == null)
1875 featureValues = new HashMap<>();
1876 theMap.put(type, featureValues);
1878 for (int[] range : mappedRanges)
1880 addAtomSpecRange(featureValues, value, modelId, range[0],
1881 range[1], mapping.getChain());
1888 * Returns the number of structure files in the structure viewer and mapped to
1889 * Jalview. This may be zero if the files are still in the process of loading
1894 public int getMappedStructureCount()
1896 String[] files = getStructureFiles();
1897 return files == null ? 0 : files.length;
1901 * Starts a thread that waits for the external viewer program process to
1902 * finish, so that we can then close the associated resources. This avoids
1903 * leaving orphaned viewer panels in Jalview if the user closes the external
1908 protected void startExternalViewerMonitor(Process p)
1910 externalViewerMonitor = new Thread(new Runnable()
1919 JalviewStructureDisplayI display = getViewer();
1920 if (display != null)
1922 display.closeViewer(false);
1924 } catch (InterruptedException e)
1926 // exit thread if Chimera Viewer is closed in Jalview
1930 externalViewerMonitor.start();
1934 * If supported by the external structure viewer, sends it commands to notify
1935 * model or selection changes to the specified URL (where Jalview has started
1940 protected void startListening(String uri)
1942 List<StructureCommandI> commands = getCommandGenerator()
1943 .startNotifications(uri);
1944 if (commands != null)
1946 executeCommands(commands, false, null);
1951 * If supported by the external structure viewer, sends it commands to stop
1952 * notifying model or selection changes
1954 protected void stopListening()
1956 List<StructureCommandI> commands = getCommandGenerator()
1957 .stopNotifications();
1958 if (commands != null)
1960 executeCommands(commands, false, null);
1965 * If supported by the structure viewer, queries it for all residue attributes
1966 * with the given attribute name, and creates features on corresponding
1967 * residues of the alignment. Returns the number of features added.
1970 * @param alignmentPanel
1973 public int copyStructureAttributesToFeatures(String attName,
1974 AlignmentPanel alignmentPanel)
1976 StructureCommandI cmd = getCommandGenerator()
1977 .getResidueAttributes(attName);
1982 List<String> residueAttributes = executeCommand(cmd, true);
1984 int featuresAdded = createFeaturesForAttributes(attName,
1986 if (featuresAdded > 0)
1988 alignmentPanel.getFeatureRenderer().featuresAdded();
1990 return featuresAdded;
1994 * Parses {@code residueAttributes} and creates sequence features on any
1995 * mapped alignment residues. Returns the number of features created.
1997 * {@code residueAttributes} is the reply from the structure viewer to a
1998 * command to list any residue attributes for the given attribute name. Syntax
1999 * and parsing of this is viewer-specific.
2002 * @param residueAttributes
2005 protected int createFeaturesForAttributes(String attName,
2006 List<String> residueAttributes)
2012 * list the ligands available for display/hiding in the current view
2014 * @return HETATM CODE:Molecule name
2016 public Map<String, String> getHetatmNames()
2018 return Collections.EMPTY_MAP;
2022 * Generates and executes a command to show the given hetatm types as CPK
2025 * - one or more of strings from getHetatmNames
2027 public void showHetatms(List<String> toShow)
2029 executeCommands(commandGenerator.showHetatms(toShow), false,
2030 "Adjusting hetatm visibility");