2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3 * Copyright (C) 2014 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.structure;
23 import jalview.analysis.AlignSeq;
24 import jalview.api.StructureSelectionManagerProvider;
25 import jalview.commands.CommandI;
26 import jalview.commands.EditCommand;
27 import jalview.commands.OrderCommand;
28 import jalview.datamodel.AlignedCodonFrame;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.Annotation;
32 import jalview.datamodel.PDBEntry;
33 import jalview.datamodel.SearchResults;
34 import jalview.datamodel.SequenceI;
35 import jalview.io.AppletFormatAdapter;
36 import jalview.util.MappingUtils;
37 import jalview.util.MessageManager;
39 import java.io.PrintStream;
40 import java.util.ArrayList;
41 import java.util.Enumeration;
42 import java.util.HashMap;
43 import java.util.IdentityHashMap;
44 import java.util.LinkedHashSet;
45 import java.util.List;
47 import java.util.Vector;
50 import MCview.PDBChain;
52 public class StructureSelectionManager
54 static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
56 private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
58 private boolean processSecondaryStructure = false;
60 private boolean secStructServices = false;
62 private boolean addTempFacAnnot = false;
64 private Set<AlignedCodonFrame> seqmappings = new LinkedHashSet<AlignedCodonFrame>();
66 private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
68 private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
71 * @return true if will try to use external services for processing secondary
74 public boolean isSecStructServices()
76 return secStructServices;
80 * control use of external services for processing secondary structure
82 * @param secStructServices
84 public void setSecStructServices(boolean secStructServices)
86 this.secStructServices = secStructServices;
90 * flag controlling addition of any kind of structural annotation
92 * @return true if temperature factor annotation will be added
94 public boolean isAddTempFacAnnot()
96 return addTempFacAnnot;
100 * set flag controlling addition of structural annotation
102 * @param addTempFacAnnot
104 public void setAddTempFacAnnot(boolean addTempFacAnnot)
106 this.addTempFacAnnot = addTempFacAnnot;
111 * @return if true, the structure manager will attempt to add secondary
112 * structure lines for unannotated sequences
115 public boolean isProcessSecondaryStructure()
117 return processSecondaryStructure;
121 * Control whether structure manager will try to annotate mapped sequences
122 * with secondary structure from PDB data.
126 public void setProcessSecondaryStructure(boolean enable)
128 processSecondaryStructure = enable;
132 * debug function - write all mappings to stdout
134 public void reportMapping()
136 if (mappings.isEmpty())
138 System.err.println("reportMapping: No PDB/Sequence mappings.");
142 System.err.println("reportMapping: There are " + mappings.size()
145 for (StructureMapping sm : mappings)
147 System.err.println("mapping " + i++ + " : " + sm.pdbfile);
153 * map between the PDB IDs (or structure identifiers) used by Jalview and the
154 * absolute filenames for PDB data that corresponds to it
156 HashMap<String, String> pdbIdFileName = new HashMap<String, String>(),
157 pdbFileNameId = new HashMap<String, String>();
159 public void registerPDBFile(String idForFile, String absoluteFile)
161 pdbIdFileName.put(idForFile, absoluteFile);
162 pdbFileNameId.put(absoluteFile, idForFile);
165 public String findIdForPDBFile(String idOrFile)
167 String id = pdbFileNameId.get(idOrFile);
171 public String findFileForPDBId(String idOrFile)
173 String id = pdbIdFileName.get(idOrFile);
177 public boolean isPDBFileRegistered(String idOrFile)
179 return pdbFileNameId.containsKey(idOrFile)
180 || pdbIdFileName.containsKey(idOrFile);
183 private static StructureSelectionManager nullProvider = null;
185 public static StructureSelectionManager getStructureSelectionManager(
186 StructureSelectionManagerProvider context)
190 if (nullProvider == null)
192 if (instances != null)
194 throw new Error(MessageManager.getString("error.implementation_error_structure_selection_manager_null"),
195 new NullPointerException(MessageManager.getString("exception.ssm_context_is_null")));
199 nullProvider = new StructureSelectionManager();
204 if (instances == null)
206 instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
208 StructureSelectionManager instance = instances.get(context);
209 if (instance == null)
211 if (nullProvider != null)
213 instance = nullProvider;
217 instance = new StructureSelectionManager();
219 instances.put(context, instance);
225 * flag controlling whether SeqMappings are relayed from received sequence
226 * mouse over events to other sequences
228 boolean relaySeqMappings = true;
231 * Enable or disable relay of seqMapping events to other sequences. You might
232 * want to do this if there are many sequence mappings and the host computer
237 public void setRelaySeqMappings(boolean relay)
239 relaySeqMappings = relay;
243 * get the state of the relay seqMappings flag.
245 * @return true if sequence mouse overs are being relayed to other mapped
248 public boolean isRelaySeqMappingsEnabled()
250 return relaySeqMappings;
253 Vector listeners = new Vector();
256 * register a listener for alignment sequence mouseover events
260 public void addStructureViewerListener(Object svl)
262 if (!listeners.contains(svl))
264 listeners.addElement(svl);
269 * Returns the file name for a mapped PDB id (or null if not mapped).
274 public String alreadyMappedToFile(String pdbid)
276 for (StructureMapping sm : mappings)
278 if (sm.getPdbId().equals(pdbid))
287 * Import structure data and register a structure mapping for broadcasting
288 * colouring, mouseovers and selection events (convenience wrapper).
291 * - one or more sequences to be mapped to pdbFile
292 * @param targetChains
293 * - optional chain specification for mapping each sequence to pdb
294 * (may be nill, individual elements may be nill)
296 * - structure data resource
298 * - how to resolve data from resource
299 * @return null or the structure data parsed as a pdb file
301 synchronized public MCview.PDBfile setMapping(SequenceI[] sequence,
302 String[] targetChains, String pdbFile, String protocol)
304 return setMapping(true, sequence, targetChains, pdbFile, protocol);
308 * create sequence structure mappings between each sequence and the given
309 * pdbFile (retrieved via the given protocol).
311 * @param forStructureView
312 * when true, record the mapping for use in mouseOvers
315 * - one or more sequences to be mapped to pdbFile
316 * @param targetChains
317 * - optional chain specification for mapping each sequence to pdb
318 * (may be nill, individual elements may be nill)
320 * - structure data resource
322 * - how to resolve data from resource
323 * @return null or the structure data parsed as a pdb file
325 synchronized public MCview.PDBfile setMapping(boolean forStructureView,
326 SequenceI[] sequence,
327 String[] targetChains, String pdbFile, String protocol)
330 * There will be better ways of doing this in the future, for now we'll use
331 * the tried and tested MCview pdb mapping
333 MCview.PDBfile pdb = null;
334 boolean parseSecStr = processSecondaryStructure;
335 if (isPDBFileRegistered(pdbFile))
337 for (SequenceI sq : sequence)
340 while (ds.getDatasetSequence() != null)
342 ds = ds.getDatasetSequence();
345 if (ds.getAnnotation() != null)
347 for (AlignmentAnnotation ala : ds.getAnnotation())
349 // false if any annotation present from this structure
350 // JBPNote this fails for jmol/chimera view because the *file* is
351 // passed, not the structure data ID -
352 if (MCview.PDBfile.isCalcIdForFile(ala,
353 findIdForPDBFile(pdbFile)))
363 pdb = new MCview.PDBfile(addTempFacAnnot, parseSecStr,
364 secStructServices, pdbFile, protocol);
365 if (pdb.id != null && pdb.id.trim().length() > 0
366 && AppletFormatAdapter.FILE.equals(protocol))
368 registerPDBFile(pdb.id.trim(), pdbFile);
370 } catch (Exception ex)
372 ex.printStackTrace();
377 for (int s = 0; s < sequence.length; s++)
379 boolean infChain = true;
380 if (targetChains != null && targetChains[s] != null)
383 targetChain = targetChains[s];
385 else if (sequence[s].getName().indexOf("|") > -1)
387 targetChain = sequence[s].getName().substring(
388 sequence[s].getName().lastIndexOf("|") + 1);
389 if (targetChain.length() > 1)
391 if (targetChain.trim().length() == 0)
397 // not a valid chain identifier
408 AlignSeq maxAlignseq = null;
409 String maxChainId = " ";
410 PDBChain maxChain = null;
411 boolean first = true;
412 for (int i = 0; i < pdb.chains.size(); i++)
414 PDBChain chain = (pdb.chains.elementAt(i));
415 if (targetChain.length() > 0 && !targetChain.equals(chain.id)
418 continue; // don't try to map chains don't match.
420 // TODO: correctly determine sequence type for mixed na/peptide
422 AlignSeq as = new AlignSeq(sequence[s],
423 pdb.chains.elementAt(i).sequence,
424 pdb.chains.elementAt(i).isNa ? AlignSeq.DNA
426 as.calcScoreMatrix();
429 if (first || as.maxscore > max
430 || (as.maxscore == max && chain.id.equals(targetChain)))
436 maxChainId = chain.id;
439 if (maxChain == null)
443 final StringBuffer mappingDetails = new StringBuffer();
444 mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
445 + maxChain.sequence.getSequenceAsString());
446 mappingDetails.append("\nNo of residues = "
447 + maxChain.residues.size() + "\n\n");
448 PrintStream ps = new PrintStream(System.out)
451 public void print(String x)
453 mappingDetails.append(x);
457 public void println()
459 mappingDetails.append("\n");
463 maxAlignseq.printAlignment(ps);
465 mappingDetails.append("\nPDB start/end " + maxAlignseq.seq2start
466 + " " + maxAlignseq.seq2end);
467 mappingDetails.append("\nSEQ start/end "
468 + (maxAlignseq.seq1start + sequence[s].getStart() - 1) + " "
469 + (maxAlignseq.seq1end + sequence[s].getEnd() - 1));
471 maxChain.makeExactMapping(maxAlignseq, sequence[s]);
472 jalview.datamodel.Mapping sqmpping = maxAlignseq
473 .getMappingFromS1(false);
474 jalview.datamodel.Mapping omap = new jalview.datamodel.Mapping(
475 sqmpping.getMap().getInverse());
476 maxChain.transferRESNUMFeatures(sequence[s], null);
478 // allocate enough slots to store the mapping from positions in
479 // sequence[s] to the associated chain
480 int[][] mapping = new int[sequence[s].findPosition(sequence[s]
481 .getLength()) + 2][2];
487 Atom tmp = (Atom) maxChain.atoms.elementAt(index);
488 if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
490 resNum = tmp.resNumber;
491 mapping[tmp.alignmentMapping + 1][0] = tmp.resNumber;
492 mapping[tmp.alignmentMapping + 1][1] = tmp.atomIndex;
496 } while (index < maxChain.atoms.size());
498 if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
500 pdbFile = "INLINE" + pdb.id;
502 StructureMapping newMapping = new StructureMapping(sequence[s],
503 pdbFile, pdb.id, maxChainId, mapping,
504 mappingDetails.toString());
505 if (forStructureView)
507 mappings.add(newMapping);
509 maxChain.transferResidueAnnotation(newMapping, sqmpping);
516 public void removeStructureViewerListener(Object svl, String[] pdbfiles)
518 listeners.removeElement(svl);
519 if (svl instanceof SequenceListener)
521 for (int i = 0; i < listeners.size(); i++)
523 if (listeners.elementAt(i) instanceof StructureListener)
525 ((StructureListener) listeners.elementAt(i))
526 .releaseReferences(svl);
531 if (pdbfiles == null)
536 Vector pdbs = new Vector();
537 for (int i = 0; i < pdbfiles.length; pdbs.addElement(pdbfiles[i++]))
541 StructureListener sl;
542 for (int i = 0; i < listeners.size(); i++)
544 if (listeners.elementAt(i) instanceof StructureListener)
546 sl = (StructureListener) listeners.elementAt(i);
547 handlepdbs = sl.getPdbFile();
548 for (int j = 0; j < handlepdbs.length; j++)
550 if (pdbs.contains(handlepdbs[j]))
552 pdbs.removeElement(handlepdbs[j]);
561 List<StructureMapping> tmp = new ArrayList<StructureMapping>();
562 for (StructureMapping sm : mappings)
564 if (!pdbs.contains(sm.pdbfile))
574 public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
576 if (listeners == null)
578 // old or prematurely sent event
581 SearchResults results = null;
582 SequenceI lastseq = null;
583 int lastipos = -1, indexpos;
584 for (int i = 0; i < listeners.size(); i++)
586 if (listeners.elementAt(i) instanceof SequenceListener)
590 results = new SearchResults();
592 for (StructureMapping sm : mappings)
594 if (sm.pdbfile.equals(pdbfile) && sm.pdbchain.equals(chain))
596 indexpos = sm.getSeqPos(pdbResNum);
597 if (lastipos != indexpos && lastseq != sm.sequence)
599 results.addResult(sm.sequence, indexpos, indexpos);
601 lastseq = sm.sequence;
602 // construct highlighted sequence list
603 for (AlignedCodonFrame acf : seqmappings)
605 acf.markMappedRegion(sm.sequence, indexpos, results);
614 for (int i = 0; i < listeners.size(); i++)
616 Object li = listeners.elementAt(i);
617 if (li instanceof SequenceListener)
619 ((SequenceListener) li).highlightSequence(results);
626 * highlight regions associated with a position (indexpos) in seq
629 * the sequence that the mouse over occurred on
631 * the absolute position being mouseovered in seq (0 to seq.length())
633 * the sequence position (if -1, seq.findPosition is called to
634 * resolve the residue number)
636 public void mouseOverSequence(SequenceI seq, int indexpos, int index,
639 boolean hasSequenceListeners = handlingVamsasMo
640 || !seqmappings.isEmpty();
641 SearchResults results = null;
644 index = seq.findPosition(indexpos);
646 for (int i = 0; i < listeners.size(); i++)
648 Object listener = listeners.elementAt(i);
649 if (listener == source)
651 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
652 // Temporary fudge with SequenceListener.getVamsasSource()
655 if (listener instanceof StructureListener)
657 highlightStructure((StructureListener) listener, seq, index);
661 if (listener instanceof SequenceListener)
663 final SequenceListener seqListener = (SequenceListener) listener;
664 if (hasSequenceListeners
665 && seqListener.getVamsasSource() != source)
667 if (relaySeqMappings)
671 results = MappingUtils.buildSearchResults(seq, index,
674 if (handlingVamsasMo)
676 results.addResult(seq, index, index);
679 seqListener.highlightSequence(results);
683 else if (listener instanceof VamsasListener && !handlingVamsasMo)
685 ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
688 else if (listener instanceof SecondaryStructureListener)
690 ((SecondaryStructureListener) listener).mouseOverSequence(seq,
698 * Send suitable messages to a StructureListener to highlight atoms
699 * corresponding to the given sequence position.
705 protected void highlightStructure(StructureListener sl, SequenceI seq,
709 List<AtomSpec> atoms = new ArrayList<AtomSpec>();
710 for (StructureMapping sm : mappings)
712 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
714 atomNo = sm.getAtomNum(index);
718 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
719 .getPDBResNum(index), atomNo));
723 sl.highlightAtoms(atoms);
727 * true if a mouse over event from an external (ie Vamsas) source is being
730 boolean handlingVamsasMo = false;
735 * as mouseOverSequence but only route event to SequenceListeners
739 * in an alignment sequence
741 public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
744 handlingVamsasMo = true;
745 long msg = sequenceI.hashCode() * (1 + position);
749 mouseOverSequence(sequenceI, position, -1, source);
751 handlingVamsasMo = false;
754 public Annotation[] colourSequenceFromStructure(SequenceI seq,
758 // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
759 // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
761 * Annotation [] annotations = new Annotation[seq.getLength()];
763 * StructureListener sl; int atomNo = 0; for (int i = 0; i <
764 * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
765 * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
767 * for (int j = 0; j < mappings.length; j++) {
769 * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
770 * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
771 * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
772 * "+mappings[j].pdbfile);
774 * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
775 * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
777 * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
778 * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
779 * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
780 * mappings[j].pdbfile); }
782 * annotations[index] = new Annotation("X",null,' ',0,col); } return
783 * annotations; } } } }
785 * return annotations;
789 public void structureSelectionChanged()
793 public void sequenceSelectionChanged()
797 public void sequenceColoursChanged(Object source)
799 StructureListener sl;
800 for (int i = 0; i < listeners.size(); i++)
802 if (listeners.elementAt(i) instanceof StructureListener)
804 sl = (StructureListener) listeners.elementAt(i);
805 sl.updateColours(source);
810 public StructureMapping[] getMapping(String pdbfile)
812 List<StructureMapping> tmp = new ArrayList<StructureMapping>();
813 for (StructureMapping sm : mappings)
815 if (sm.pdbfile.equals(pdbfile))
820 return tmp.toArray(new StructureMapping[tmp.size()]);
823 public String printMapping(String pdbfile)
825 StringBuilder sb = new StringBuilder(64);
826 for (StructureMapping sm : mappings)
828 if (sm.pdbfile.equals(pdbfile))
830 sb.append(sm.mappingDetails);
834 return sb.toString();
838 * Remove each of the given codonFrames from the stored set (if present).
842 public void removeMappings(AlignedCodonFrame[] codonFrames)
844 if (codonFrames != null)
846 for (AlignedCodonFrame acf : codonFrames)
848 seqmappings.remove(acf);
854 * Add each of the given codonFrames to the stored set (if not aready
859 public void addMappings(AlignedCodonFrame[] codonFrames)
861 if (codonFrames != null)
863 for (AlignedCodonFrame acf : codonFrames)
865 seqmappings.add(acf);
870 public void addSelectionListener(SelectionListener selecter)
872 if (!sel_listeners.contains(selecter))
874 sel_listeners.add(selecter);
878 public void removeSelectionListener(SelectionListener toremove)
880 if (sel_listeners.contains(toremove))
882 sel_listeners.remove(toremove);
886 public synchronized void sendSelection(
887 jalview.datamodel.SequenceGroup selection,
888 jalview.datamodel.ColumnSelection colsel, SelectionSource source)
890 for (SelectionListener slis : sel_listeners)
894 slis.selection(selection, colsel, source);
899 Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
901 public synchronized void sendViewPosition(
902 jalview.api.AlignmentViewPanel source, int startRes, int endRes,
903 int startSeq, int endSeq)
906 if (view_listeners != null && view_listeners.size() > 0)
908 Enumeration<AlignmentViewPanelListener> listeners = view_listeners
910 while (listeners.hasMoreElements())
912 AlignmentViewPanelListener slis = listeners.nextElement();
915 slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
923 * release all references associated with this manager provider
927 public static void release(StructureSelectionManagerProvider jalviewLite)
929 // synchronized (instances)
931 if (instances == null)
935 StructureSelectionManager mnger = (instances.get(jalviewLite));
938 instances.remove(jalviewLite);
942 } catch (Throwable x)
949 public void registerPDBEntry(PDBEntry pdbentry)
951 if (pdbentry.getFile() != null
952 && pdbentry.getFile().trim().length() > 0)
954 registerPDBFile(pdbentry.getId(), pdbentry.getFile());
958 public void addCommandListener(CommandListener cl)
960 if (!commandListeners.contains(cl))
962 commandListeners.add(cl);
966 public boolean hasCommandListener(CommandListener cl)
968 return this.commandListeners.contains(cl);
971 public boolean removeEditListener(CommandListener l)
973 return commandListeners.remove(l);
977 * Forward a command to any command listeners (except for the command's
981 * the command to be broadcast (in its form after being performed)
983 * if true, the command was being 'undone'
986 public void commandPerformed(CommandI command, boolean undo,
989 for (CommandListener listener : commandListeners)
991 listener.mirrorCommand(command, undo, this, source);
996 * Returns a new CommandI representing the given command as mapped to the
997 * given sequences. If no mapping could be made, or the command is not of a
998 * mappable kind, returns null.
1006 public CommandI mapCommand(CommandI command,
1008 final AlignmentI alignmentI, char gapChar)
1010 if (command instanceof EditCommand)
1012 return MappingUtils.mapEditCommand((EditCommand) command, undo,
1013 alignmentI, gapChar,
1016 else if (command instanceof OrderCommand)
1018 return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1019 alignmentI, seqmappings);