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.structure;
23 import java.io.PrintStream;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.IdentityHashMap;
29 import java.util.LinkedHashSet;
30 import java.util.List;
33 import java.util.Vector;
36 import MCview.PDBChain;
38 import jalview.analysis.AlignSeq;
39 import jalview.api.StructureSelectionManagerProvider;
40 import jalview.commands.CommandI;
41 import jalview.commands.EditCommand;
42 import jalview.commands.OrderCommand;
43 import jalview.datamodel.AlignedCodonFrame;
44 import jalview.datamodel.AlignmentAnnotation;
45 import jalview.datamodel.AlignmentI;
46 import jalview.datamodel.Annotation;
47 import jalview.datamodel.PDBEntry;
48 import jalview.datamodel.SearchResults;
49 import jalview.datamodel.SequenceI;
50 import jalview.io.AppletFormatAdapter;
51 import jalview.util.MappingUtils;
52 import jalview.util.MessageManager;
54 public class StructureSelectionManager
56 static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
58 private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
60 private boolean processSecondaryStructure = false;
62 private boolean secStructServices = false;
64 private boolean addTempFacAnnot = false;
67 * Set of any registered mappings between (dataset) sequences.
69 Set<AlignedCodonFrame> seqmappings = new LinkedHashSet<AlignedCodonFrame>();
72 * Reference counters for the above mappings. Remove mappings when ref count
75 Map<AlignedCodonFrame, Integer> seqMappingRefCounts = new HashMap<AlignedCodonFrame, Integer>();
77 private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
79 private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
82 * @return true if will try to use external services for processing secondary
85 public boolean isSecStructServices()
87 return secStructServices;
91 * control use of external services for processing secondary structure
93 * @param secStructServices
95 public void setSecStructServices(boolean secStructServices)
97 this.secStructServices = secStructServices;
101 * flag controlling addition of any kind of structural annotation
103 * @return true if temperature factor annotation will be added
105 public boolean isAddTempFacAnnot()
107 return addTempFacAnnot;
111 * set flag controlling addition of structural annotation
113 * @param addTempFacAnnot
115 public void setAddTempFacAnnot(boolean addTempFacAnnot)
117 this.addTempFacAnnot = addTempFacAnnot;
122 * @return if true, the structure manager will attempt to add secondary
123 * structure lines for unannotated sequences
126 public boolean isProcessSecondaryStructure()
128 return processSecondaryStructure;
132 * Control whether structure manager will try to annotate mapped sequences
133 * with secondary structure from PDB data.
137 public void setProcessSecondaryStructure(boolean enable)
139 processSecondaryStructure = enable;
143 * debug function - write all mappings to stdout
145 public void reportMapping()
147 if (mappings.isEmpty())
149 System.err.println("reportMapping: No PDB/Sequence mappings.");
153 System.err.println("reportMapping: There are " + mappings.size()
156 for (StructureMapping sm : mappings)
158 System.err.println("mapping " + i++ + " : " + sm.pdbfile);
164 * map between the PDB IDs (or structure identifiers) used by Jalview and the
165 * absolute filenames for PDB data that corresponds to it
167 HashMap<String, String> pdbIdFileName = new HashMap<String, String>(),
168 pdbFileNameId = new HashMap<String, String>();
170 public void registerPDBFile(String idForFile, String absoluteFile)
172 pdbIdFileName.put(idForFile, absoluteFile);
173 pdbFileNameId.put(absoluteFile, idForFile);
176 public String findIdForPDBFile(String idOrFile)
178 String id = pdbFileNameId.get(idOrFile);
182 public String findFileForPDBId(String idOrFile)
184 String id = pdbIdFileName.get(idOrFile);
188 public boolean isPDBFileRegistered(String idOrFile)
190 return pdbFileNameId.containsKey(idOrFile)
191 || pdbIdFileName.containsKey(idOrFile);
194 private static StructureSelectionManager nullProvider = null;
196 public static StructureSelectionManager getStructureSelectionManager(
197 StructureSelectionManagerProvider context)
201 if (nullProvider == null)
203 if (instances != null)
205 throw new Error(MessageManager.getString("error.implementation_error_structure_selection_manager_null"),
206 new NullPointerException(MessageManager.getString("exception.ssm_context_is_null")));
210 nullProvider = new StructureSelectionManager();
215 if (instances == null)
217 instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
219 StructureSelectionManager instance = instances.get(context);
220 if (instance == null)
222 if (nullProvider != null)
224 instance = nullProvider;
228 instance = new StructureSelectionManager();
230 instances.put(context, instance);
236 * flag controlling whether SeqMappings are relayed from received sequence
237 * mouse over events to other sequences
239 boolean relaySeqMappings = true;
242 * Enable or disable relay of seqMapping events to other sequences. You might
243 * want to do this if there are many sequence mappings and the host computer
248 public void setRelaySeqMappings(boolean relay)
250 relaySeqMappings = relay;
254 * get the state of the relay seqMappings flag.
256 * @return true if sequence mouse overs are being relayed to other mapped
259 public boolean isRelaySeqMappingsEnabled()
261 return relaySeqMappings;
264 Vector listeners = new Vector();
267 * register a listener for alignment sequence mouseover events
271 public void addStructureViewerListener(Object svl)
273 if (!listeners.contains(svl))
275 listeners.addElement(svl);
280 * Returns the file name for a mapped PDB id (or null if not mapped).
285 public String alreadyMappedToFile(String pdbid)
287 for (StructureMapping sm : mappings)
289 if (sm.getPdbId().equals(pdbid))
298 * Import structure data and register a structure mapping for broadcasting
299 * colouring, mouseovers and selection events (convenience wrapper).
302 * - one or more sequences to be mapped to pdbFile
303 * @param targetChains
304 * - optional chain specification for mapping each sequence to pdb
305 * (may be nill, individual elements may be nill)
307 * - structure data resource
309 * - how to resolve data from resource
310 * @return null or the structure data parsed as a pdb file
312 synchronized public MCview.PDBfile setMapping(SequenceI[] sequence,
313 String[] targetChains, String pdbFile, String protocol)
315 return setMapping(true, sequence, targetChains, pdbFile, protocol);
319 * create sequence structure mappings between each sequence and the given
320 * pdbFile (retrieved via the given protocol).
322 * @param forStructureView
323 * when true, record the mapping for use in mouseOvers
326 * - one or more sequences to be mapped to pdbFile
327 * @param targetChains
328 * - optional chain specification for mapping each sequence to pdb
329 * (may be nill, individual elements may be nill)
331 * - structure data resource
333 * - how to resolve data from resource
334 * @return null or the structure data parsed as a pdb file
336 synchronized public MCview.PDBfile setMapping(boolean forStructureView,
337 SequenceI[] sequence,
338 String[] targetChains, String pdbFile, String protocol)
341 * There will be better ways of doing this in the future, for now we'll use
342 * the tried and tested MCview pdb mapping
344 MCview.PDBfile pdb = null;
345 boolean parseSecStr = processSecondaryStructure;
346 if (isPDBFileRegistered(pdbFile))
348 for (SequenceI sq : sequence)
351 while (ds.getDatasetSequence() != null)
353 ds = ds.getDatasetSequence();
356 if (ds.getAnnotation() != null)
358 for (AlignmentAnnotation ala : ds.getAnnotation())
360 // false if any annotation present from this structure
361 // JBPNote this fails for jmol/chimera view because the *file* is
362 // passed, not the structure data ID -
363 if (MCview.PDBfile.isCalcIdForFile(ala,
364 findIdForPDBFile(pdbFile)))
374 pdb = new MCview.PDBfile(addTempFacAnnot, parseSecStr,
375 secStructServices, pdbFile, protocol);
376 if (pdb.id != null && pdb.id.trim().length() > 0
377 && AppletFormatAdapter.FILE.equals(protocol))
379 registerPDBFile(pdb.id.trim(), pdbFile);
381 } catch (Exception ex)
383 ex.printStackTrace();
388 for (int s = 0; s < sequence.length; s++)
390 boolean infChain = true;
391 if (targetChains != null && targetChains[s] != null)
394 targetChain = targetChains[s];
396 else if (sequence[s].getName().indexOf("|") > -1)
398 targetChain = sequence[s].getName().substring(
399 sequence[s].getName().lastIndexOf("|") + 1);
400 if (targetChain.length() > 1)
402 if (targetChain.trim().length() == 0)
408 // not a valid chain identifier
419 AlignSeq maxAlignseq = null;
420 String maxChainId = " ";
421 PDBChain maxChain = null;
422 boolean first = true;
423 for (int i = 0; i < pdb.chains.size(); i++)
425 PDBChain chain = (pdb.chains.elementAt(i));
426 if (targetChain.length() > 0 && !targetChain.equals(chain.id)
429 continue; // don't try to map chains don't match.
431 // TODO: correctly determine sequence type for mixed na/peptide
433 AlignSeq as = new AlignSeq(sequence[s],
434 pdb.chains.elementAt(i).sequence,
435 pdb.chains.elementAt(i).isNa ? AlignSeq.DNA
437 as.calcScoreMatrix();
440 if (first || as.maxscore > max
441 || (as.maxscore == max && chain.id.equals(targetChain)))
447 maxChainId = chain.id;
450 if (maxChain == null)
454 final StringBuffer mappingDetails = new StringBuffer();
455 mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
456 + maxChain.sequence.getSequenceAsString());
457 mappingDetails.append("\nNo of residues = "
458 + maxChain.residues.size() + "\n\n");
459 PrintStream ps = new PrintStream(System.out)
462 public void print(String x)
464 mappingDetails.append(x);
468 public void println()
470 mappingDetails.append("\n");
474 maxAlignseq.printAlignment(ps);
476 mappingDetails.append("\nPDB start/end " + maxAlignseq.seq2start
477 + " " + maxAlignseq.seq2end);
478 mappingDetails.append("\nSEQ start/end "
479 + (maxAlignseq.seq1start + sequence[s].getStart() - 1) + " "
480 + (maxAlignseq.seq1end + sequence[s].getEnd() - 1));
482 maxChain.makeExactMapping(maxAlignseq, sequence[s]);
483 jalview.datamodel.Mapping sqmpping = maxAlignseq
484 .getMappingFromS1(false);
485 jalview.datamodel.Mapping omap = new jalview.datamodel.Mapping(
486 sqmpping.getMap().getInverse());
487 maxChain.transferRESNUMFeatures(sequence[s], null);
489 // allocate enough slots to store the mapping from positions in
490 // sequence[s] to the associated chain
491 int[][] mapping = new int[sequence[s].findPosition(sequence[s]
492 .getLength()) + 2][2];
498 Atom tmp = (Atom) maxChain.atoms.elementAt(index);
499 if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
501 resNum = tmp.resNumber;
502 mapping[tmp.alignmentMapping + 1][0] = tmp.resNumber;
503 mapping[tmp.alignmentMapping + 1][1] = tmp.atomIndex;
507 } while (index < maxChain.atoms.size());
509 if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
511 pdbFile = "INLINE" + pdb.id;
513 StructureMapping newMapping = new StructureMapping(sequence[s],
514 pdbFile, pdb.id, maxChainId, mapping,
515 mappingDetails.toString());
516 if (forStructureView)
518 mappings.add(newMapping);
520 maxChain.transferResidueAnnotation(newMapping, sqmpping);
527 public void removeStructureViewerListener(Object svl, String[] pdbfiles)
529 listeners.removeElement(svl);
530 if (svl instanceof SequenceListener)
532 for (int i = 0; i < listeners.size(); i++)
534 if (listeners.elementAt(i) instanceof StructureListener)
536 ((StructureListener) listeners.elementAt(i))
537 .releaseReferences(svl);
542 if (pdbfiles == null)
547 Vector pdbs = new Vector();
548 for (int i = 0; i < pdbfiles.length; pdbs.addElement(pdbfiles[i++]))
552 StructureListener sl;
553 for (int i = 0; i < listeners.size(); i++)
555 if (listeners.elementAt(i) instanceof StructureListener)
557 sl = (StructureListener) listeners.elementAt(i);
558 handlepdbs = sl.getPdbFile();
559 for (int j = 0; j < handlepdbs.length; j++)
561 if (pdbs.contains(handlepdbs[j]))
563 pdbs.removeElement(handlepdbs[j]);
572 List<StructureMapping> tmp = new ArrayList<StructureMapping>();
573 for (StructureMapping sm : mappings)
575 if (!pdbs.contains(sm.pdbfile))
586 * Propagate mouseover of a single position in a structure
592 public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
594 AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
595 List<AtomSpec> atoms = Collections.singletonList(atomSpec);
596 mouseOverStructure(atoms);
600 * Propagate mouseover or selection of multiple positions in a structure
604 public void mouseOverStructure(List<AtomSpec> atoms)
606 if (listeners == null)
608 // old or prematurely sent event
611 boolean hasSequenceListener = false;
612 for (int i = 0; i < listeners.size(); i++)
614 if (listeners.elementAt(i) instanceof SequenceListener)
616 hasSequenceListener = true;
619 if (!hasSequenceListener)
624 SearchResults results = new SearchResults();
625 for (AtomSpec atom : atoms)
627 SequenceI lastseq = null;
629 for (StructureMapping sm : mappings)
631 if (sm.pdbfile.equals(atom.getPdbFile())
632 && sm.pdbchain.equals(atom.getChain()))
634 int indexpos = sm.getSeqPos(atom.getPdbResNum());
635 if (lastipos != indexpos && lastseq != sm.sequence)
637 results.addResult(sm.sequence, indexpos, indexpos);
639 lastseq = sm.sequence;
640 // construct highlighted sequence list
641 for (AlignedCodonFrame acf : seqmappings)
643 acf.markMappedRegion(sm.sequence, indexpos, results);
649 for (Object li : listeners)
651 if (li instanceof SequenceListener)
653 ((SequenceListener) li).highlightSequence(results);
659 * highlight regions associated with a position (indexpos) in seq
662 * the sequence that the mouse over occurred on
664 * the absolute position being mouseovered in seq (0 to seq.length())
666 * the sequence position (if -1, seq.findPosition is called to
667 * resolve the residue number)
669 public void mouseOverSequence(SequenceI seq, int indexpos, int index,
672 boolean hasSequenceListeners = handlingVamsasMo
673 || !seqmappings.isEmpty();
674 SearchResults results = null;
677 index = seq.findPosition(indexpos);
679 for (int i = 0; i < listeners.size(); i++)
681 Object listener = listeners.elementAt(i);
682 if (listener == source)
684 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
685 // Temporary fudge with SequenceListener.getVamsasSource()
688 if (listener instanceof StructureListener)
690 highlightStructure((StructureListener) listener, seq, index);
694 if (listener instanceof SequenceListener)
696 final SequenceListener seqListener = (SequenceListener) listener;
697 if (hasSequenceListeners
698 && seqListener.getVamsasSource() != source)
700 if (relaySeqMappings)
704 results = MappingUtils.buildSearchResults(seq, index,
707 if (handlingVamsasMo)
709 results.addResult(seq, index, index);
712 seqListener.highlightSequence(results);
716 else if (listener instanceof VamsasListener && !handlingVamsasMo)
718 ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
721 else if (listener instanceof SecondaryStructureListener)
723 ((SecondaryStructureListener) listener).mouseOverSequence(seq,
731 * Send suitable messages to a StructureListener to highlight atoms
732 * corresponding to the given sequence position.
738 protected void highlightStructure(StructureListener sl, SequenceI seq,
742 List<AtomSpec> atoms = new ArrayList<AtomSpec>();
743 for (StructureMapping sm : mappings)
745 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
747 atomNo = sm.getAtomNum(index);
751 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
752 .getPDBResNum(index), atomNo));
756 sl.highlightAtoms(atoms);
760 * true if a mouse over event from an external (ie Vamsas) source is being
763 boolean handlingVamsasMo = false;
768 * as mouseOverSequence but only route event to SequenceListeners
772 * in an alignment sequence
774 public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
777 handlingVamsasMo = true;
778 long msg = sequenceI.hashCode() * (1 + position);
782 mouseOverSequence(sequenceI, position, -1, source);
784 handlingVamsasMo = false;
787 public Annotation[] colourSequenceFromStructure(SequenceI seq,
791 // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
792 // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
794 * Annotation [] annotations = new Annotation[seq.getLength()];
796 * StructureListener sl; int atomNo = 0; for (int i = 0; i <
797 * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
798 * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
800 * for (int j = 0; j < mappings.length; j++) {
802 * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
803 * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
804 * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
805 * "+mappings[j].pdbfile);
807 * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
808 * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
810 * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
811 * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
812 * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
813 * mappings[j].pdbfile); }
815 * annotations[index] = new Annotation("X",null,' ',0,col); } return
816 * annotations; } } } }
818 * return annotations;
822 public void structureSelectionChanged()
826 public void sequenceSelectionChanged()
830 public void sequenceColoursChanged(Object source)
832 StructureListener sl;
833 for (int i = 0; i < listeners.size(); i++)
835 if (listeners.elementAt(i) instanceof StructureListener)
837 sl = (StructureListener) listeners.elementAt(i);
838 sl.updateColours(source);
843 public StructureMapping[] getMapping(String pdbfile)
845 List<StructureMapping> tmp = new ArrayList<StructureMapping>();
846 for (StructureMapping sm : mappings)
848 if (sm.pdbfile.equals(pdbfile))
853 return tmp.toArray(new StructureMapping[tmp.size()]);
856 public String printMapping(String pdbfile)
858 StringBuilder sb = new StringBuilder(64);
859 for (StructureMapping sm : mappings)
861 if (sm.pdbfile.equals(pdbfile))
863 sb.append(sm.mappingDetails);
867 return sb.toString();
871 * Decrement the reference counter for each of the given mappings, and remove
872 * it entirely if its reference counter reduces to zero.
876 public void removeMappings(Set<AlignedCodonFrame> set)
880 for (AlignedCodonFrame acf : set)
888 * Decrement the reference counter for the given mapping, and remove it
889 * entirely if its reference counter reduces to zero.
893 public void removeMapping(AlignedCodonFrame acf)
895 if (acf != null && seqmappings.contains(acf))
897 int count = seqMappingRefCounts.get(acf);
901 seqMappingRefCounts.put(acf, count);
905 seqmappings.remove(acf);
906 seqMappingRefCounts.remove(acf);
912 * Add each of the given codonFrames to the stored set. If not aready present,
913 * increments its reference count instead.
917 public void addMappings(Set<AlignedCodonFrame> set)
921 for (AlignedCodonFrame acf : set)
929 * Add the given mapping to the stored set, or if already stored, increment
930 * its reference counter.
932 public void addMapping(AlignedCodonFrame acf)
936 if (seqmappings.contains(acf))
938 seqMappingRefCounts.put(acf, seqMappingRefCounts.get(acf) + 1);
942 seqmappings.add(acf);
943 seqMappingRefCounts.put(acf, 1);
948 public void addSelectionListener(SelectionListener selecter)
950 if (!sel_listeners.contains(selecter))
952 sel_listeners.add(selecter);
956 public void removeSelectionListener(SelectionListener toremove)
958 if (sel_listeners.contains(toremove))
960 sel_listeners.remove(toremove);
964 public synchronized void sendSelection(
965 jalview.datamodel.SequenceGroup selection,
966 jalview.datamodel.ColumnSelection colsel, SelectionSource source)
968 for (SelectionListener slis : sel_listeners)
972 slis.selection(selection, colsel, source);
977 Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
979 public synchronized void sendViewPosition(
980 jalview.api.AlignmentViewPanel source, int startRes, int endRes,
981 int startSeq, int endSeq)
984 if (view_listeners != null && view_listeners.size() > 0)
986 Enumeration<AlignmentViewPanelListener> listeners = view_listeners
988 while (listeners.hasMoreElements())
990 AlignmentViewPanelListener slis = listeners.nextElement();
993 slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1001 * release all references associated with this manager provider
1003 * @param jalviewLite
1005 public static void release(StructureSelectionManagerProvider jalviewLite)
1007 // synchronized (instances)
1009 if (instances == null)
1013 StructureSelectionManager mnger = (instances.get(jalviewLite));
1016 instances.remove(jalviewLite);
1020 } catch (Throwable x)
1027 public void registerPDBEntry(PDBEntry pdbentry)
1029 if (pdbentry.getFile() != null
1030 && pdbentry.getFile().trim().length() > 0)
1032 registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1036 public void addCommandListener(CommandListener cl)
1038 if (!commandListeners.contains(cl))
1040 commandListeners.add(cl);
1044 public boolean hasCommandListener(CommandListener cl)
1046 return this.commandListeners.contains(cl);
1049 public boolean removeCommandListener(CommandListener l)
1051 return commandListeners.remove(l);
1055 * Forward a command to any command listeners (except for the command's
1059 * the command to be broadcast (in its form after being performed)
1061 * if true, the command was being 'undone'
1064 public void commandPerformed(CommandI command, boolean undo,
1065 VamsasSource source)
1067 for (CommandListener listener : commandListeners)
1069 listener.mirrorCommand(command, undo, this, source);
1074 * Returns a new CommandI representing the given command as mapped to the
1075 * given sequences. If no mapping could be made, or the command is not of a
1076 * mappable kind, returns null.
1084 public CommandI mapCommand(CommandI command, boolean undo,
1085 final AlignmentI mapTo, char gapChar)
1087 if (command instanceof EditCommand)
1089 return MappingUtils.mapEditCommand((EditCommand) command, undo,
1090 mapTo, gapChar, seqmappings);
1092 else if (command instanceof OrderCommand)
1094 return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1095 mapTo, seqmappings);