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 if (!results.isEmpty())
651 for (Object li : listeners)
653 if (li instanceof SequenceListener)
655 ((SequenceListener) li).highlightSequence(results);
662 * highlight regions associated with a position (indexpos) in seq
665 * the sequence that the mouse over occurred on
667 * the absolute position being mouseovered in seq (0 to seq.length())
669 * the sequence position (if -1, seq.findPosition is called to
670 * resolve the residue number)
672 public void mouseOverSequence(SequenceI seq, int indexpos, int index,
675 boolean hasSequenceListeners = handlingVamsasMo
676 || !seqmappings.isEmpty();
677 SearchResults results = null;
680 index = seq.findPosition(indexpos);
682 for (int i = 0; i < listeners.size(); i++)
684 Object listener = listeners.elementAt(i);
685 if (listener == source)
687 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
688 // Temporary fudge with SequenceListener.getVamsasSource()
691 if (listener instanceof StructureListener)
693 highlightStructure((StructureListener) listener, seq, index);
697 if (listener instanceof SequenceListener)
699 final SequenceListener seqListener = (SequenceListener) listener;
700 if (hasSequenceListeners
701 && seqListener.getVamsasSource() != source)
703 if (relaySeqMappings)
707 results = MappingUtils.buildSearchResults(seq, index,
710 if (handlingVamsasMo)
712 results.addResult(seq, index, index);
715 seqListener.highlightSequence(results);
719 else if (listener instanceof VamsasListener && !handlingVamsasMo)
721 ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
724 else if (listener instanceof SecondaryStructureListener)
726 ((SecondaryStructureListener) listener).mouseOverSequence(seq,
734 * Send suitable messages to a StructureListener to highlight atoms
735 * corresponding to the given sequence position.
741 protected void highlightStructure(StructureListener sl, SequenceI seq,
745 List<AtomSpec> atoms = new ArrayList<AtomSpec>();
746 for (StructureMapping sm : mappings)
748 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
750 atomNo = sm.getAtomNum(index);
754 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
755 .getPDBResNum(index), atomNo));
759 sl.highlightAtoms(atoms);
763 * true if a mouse over event from an external (ie Vamsas) source is being
766 boolean handlingVamsasMo = false;
771 * as mouseOverSequence but only route event to SequenceListeners
775 * in an alignment sequence
777 public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
780 handlingVamsasMo = true;
781 long msg = sequenceI.hashCode() * (1 + position);
785 mouseOverSequence(sequenceI, position, -1, source);
787 handlingVamsasMo = false;
790 public Annotation[] colourSequenceFromStructure(SequenceI seq,
794 // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
795 // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
797 * Annotation [] annotations = new Annotation[seq.getLength()];
799 * StructureListener sl; int atomNo = 0; for (int i = 0; i <
800 * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
801 * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
803 * for (int j = 0; j < mappings.length; j++) {
805 * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
806 * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
807 * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
808 * "+mappings[j].pdbfile);
810 * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
811 * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
813 * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
814 * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
815 * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
816 * mappings[j].pdbfile); }
818 * annotations[index] = new Annotation("X",null,' ',0,col); } return
819 * annotations; } } } }
821 * return annotations;
825 public void structureSelectionChanged()
829 public void sequenceSelectionChanged()
833 public void sequenceColoursChanged(Object source)
835 StructureListener sl;
836 for (int i = 0; i < listeners.size(); i++)
838 if (listeners.elementAt(i) instanceof StructureListener)
840 sl = (StructureListener) listeners.elementAt(i);
841 sl.updateColours(source);
846 public StructureMapping[] getMapping(String pdbfile)
848 List<StructureMapping> tmp = new ArrayList<StructureMapping>();
849 for (StructureMapping sm : mappings)
851 if (sm.pdbfile.equals(pdbfile))
856 return tmp.toArray(new StructureMapping[tmp.size()]);
859 public String printMapping(String pdbfile)
861 StringBuilder sb = new StringBuilder(64);
862 for (StructureMapping sm : mappings)
864 if (sm.pdbfile.equals(pdbfile))
866 sb.append(sm.mappingDetails);
870 return sb.toString();
874 * Decrement the reference counter for each of the given mappings, and remove
875 * it entirely if its reference counter reduces to zero.
879 public void removeMappings(Set<AlignedCodonFrame> set)
883 for (AlignedCodonFrame acf : set)
891 * Decrement the reference counter for the given mapping, and remove it
892 * entirely if its reference counter reduces to zero.
896 public void removeMapping(AlignedCodonFrame acf)
898 if (acf != null && seqmappings.contains(acf))
900 int count = seqMappingRefCounts.get(acf);
904 seqMappingRefCounts.put(acf, count);
908 seqmappings.remove(acf);
909 seqMappingRefCounts.remove(acf);
915 * Add each of the given codonFrames to the stored set. If not aready present,
916 * increments its reference count instead.
920 public void addMappings(Set<AlignedCodonFrame> set)
924 for (AlignedCodonFrame acf : set)
932 * Add the given mapping to the stored set, or if already stored, increment
933 * its reference counter.
935 public void addMapping(AlignedCodonFrame acf)
939 if (seqmappings.contains(acf))
941 seqMappingRefCounts.put(acf, seqMappingRefCounts.get(acf) + 1);
945 seqmappings.add(acf);
946 seqMappingRefCounts.put(acf, 1);
951 public void addSelectionListener(SelectionListener selecter)
953 if (!sel_listeners.contains(selecter))
955 sel_listeners.add(selecter);
959 public void removeSelectionListener(SelectionListener toremove)
961 if (sel_listeners.contains(toremove))
963 sel_listeners.remove(toremove);
967 public synchronized void sendSelection(
968 jalview.datamodel.SequenceGroup selection,
969 jalview.datamodel.ColumnSelection colsel, SelectionSource source)
971 for (SelectionListener slis : sel_listeners)
975 slis.selection(selection, colsel, source);
980 Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
982 public synchronized void sendViewPosition(
983 jalview.api.AlignmentViewPanel source, int startRes, int endRes,
984 int startSeq, int endSeq)
987 if (view_listeners != null && view_listeners.size() > 0)
989 Enumeration<AlignmentViewPanelListener> listeners = view_listeners
991 while (listeners.hasMoreElements())
993 AlignmentViewPanelListener slis = listeners.nextElement();
996 slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1004 * release all references associated with this manager provider
1006 * @param jalviewLite
1008 public static void release(StructureSelectionManagerProvider jalviewLite)
1010 // synchronized (instances)
1012 if (instances == null)
1016 StructureSelectionManager mnger = (instances.get(jalviewLite));
1019 instances.remove(jalviewLite);
1023 } catch (Throwable x)
1030 public void registerPDBEntry(PDBEntry pdbentry)
1032 if (pdbentry.getFile() != null
1033 && pdbentry.getFile().trim().length() > 0)
1035 registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1039 public void addCommandListener(CommandListener cl)
1041 if (!commandListeners.contains(cl))
1043 commandListeners.add(cl);
1047 public boolean hasCommandListener(CommandListener cl)
1049 return this.commandListeners.contains(cl);
1052 public boolean removeCommandListener(CommandListener l)
1054 return commandListeners.remove(l);
1058 * Forward a command to any command listeners (except for the command's
1062 * the command to be broadcast (in its form after being performed)
1064 * if true, the command was being 'undone'
1067 public void commandPerformed(CommandI command, boolean undo,
1068 VamsasSource source)
1070 for (CommandListener listener : commandListeners)
1072 listener.mirrorCommand(command, undo, this, source);
1077 * Returns a new CommandI representing the given command as mapped to the
1078 * given sequences. If no mapping could be made, or the command is not of a
1079 * mappable kind, returns null.
1087 public CommandI mapCommand(CommandI command, boolean undo,
1088 final AlignmentI mapTo, char gapChar)
1090 if (command instanceof EditCommand)
1092 return MappingUtils.mapEditCommand((EditCommand) command, undo,
1093 mapTo, gapChar, seqmappings);
1095 else if (command instanceof OrderCommand)
1097 return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1098 mapTo, seqmappings);