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 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;
48 import java.util.Vector;
51 import MCview.PDBChain;
53 public class StructureSelectionManager
55 static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
57 private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
59 private boolean processSecondaryStructure = false;
61 private boolean secStructServices = false;
63 private boolean addTempFacAnnot = false;
66 * Set of any registered mappings between (dataset) sequences.
68 Set<AlignedCodonFrame> seqmappings = new LinkedHashSet<AlignedCodonFrame>();
71 * Reference counters for the above mappings. Remove mappings when ref count
74 Map<AlignedCodonFrame, Integer> seqMappingRefCounts = new HashMap<AlignedCodonFrame, Integer>();
76 private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
78 private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
81 * @return true if will try to use external services for processing secondary
84 public boolean isSecStructServices()
86 return secStructServices;
90 * control use of external services for processing secondary structure
92 * @param secStructServices
94 public void setSecStructServices(boolean secStructServices)
96 this.secStructServices = secStructServices;
100 * flag controlling addition of any kind of structural annotation
102 * @return true if temperature factor annotation will be added
104 public boolean isAddTempFacAnnot()
106 return addTempFacAnnot;
110 * set flag controlling addition of structural annotation
112 * @param addTempFacAnnot
114 public void setAddTempFacAnnot(boolean addTempFacAnnot)
116 this.addTempFacAnnot = addTempFacAnnot;
121 * @return if true, the structure manager will attempt to add secondary
122 * structure lines for unannotated sequences
125 public boolean isProcessSecondaryStructure()
127 return processSecondaryStructure;
131 * Control whether structure manager will try to annotate mapped sequences
132 * with secondary structure from PDB data.
136 public void setProcessSecondaryStructure(boolean enable)
138 processSecondaryStructure = enable;
142 * debug function - write all mappings to stdout
144 public void reportMapping()
146 if (mappings.isEmpty())
148 System.err.println("reportMapping: No PDB/Sequence mappings.");
152 System.err.println("reportMapping: There are " + mappings.size()
155 for (StructureMapping sm : mappings)
157 System.err.println("mapping " + i++ + " : " + sm.pdbfile);
163 * map between the PDB IDs (or structure identifiers) used by Jalview and the
164 * absolute filenames for PDB data that corresponds to it
166 HashMap<String, String> pdbIdFileName = new HashMap<String, String>(),
167 pdbFileNameId = new HashMap<String, String>();
169 public void registerPDBFile(String idForFile, String absoluteFile)
171 pdbIdFileName.put(idForFile, absoluteFile);
172 pdbFileNameId.put(absoluteFile, idForFile);
175 public String findIdForPDBFile(String idOrFile)
177 String id = pdbFileNameId.get(idOrFile);
181 public String findFileForPDBId(String idOrFile)
183 String id = pdbIdFileName.get(idOrFile);
187 public boolean isPDBFileRegistered(String idOrFile)
189 return pdbFileNameId.containsKey(idOrFile)
190 || pdbIdFileName.containsKey(idOrFile);
193 private static StructureSelectionManager nullProvider = null;
195 public static StructureSelectionManager getStructureSelectionManager(
196 StructureSelectionManagerProvider context)
200 if (nullProvider == null)
202 if (instances != null)
204 throw new Error(MessageManager.getString("error.implementation_error_structure_selection_manager_null"),
205 new NullPointerException(MessageManager.getString("exception.ssm_context_is_null")));
209 nullProvider = new StructureSelectionManager();
214 if (instances == null)
216 instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
218 StructureSelectionManager instance = instances.get(context);
219 if (instance == null)
221 if (nullProvider != null)
223 instance = nullProvider;
227 instance = new StructureSelectionManager();
229 instances.put(context, instance);
235 * flag controlling whether SeqMappings are relayed from received sequence
236 * mouse over events to other sequences
238 boolean relaySeqMappings = true;
241 * Enable or disable relay of seqMapping events to other sequences. You might
242 * want to do this if there are many sequence mappings and the host computer
247 public void setRelaySeqMappings(boolean relay)
249 relaySeqMappings = relay;
253 * get the state of the relay seqMappings flag.
255 * @return true if sequence mouse overs are being relayed to other mapped
258 public boolean isRelaySeqMappingsEnabled()
260 return relaySeqMappings;
263 Vector listeners = new Vector();
266 * register a listener for alignment sequence mouseover events
270 public void addStructureViewerListener(Object svl)
272 if (!listeners.contains(svl))
274 listeners.addElement(svl);
279 * Returns the file name for a mapped PDB id (or null if not mapped).
284 public String alreadyMappedToFile(String pdbid)
286 for (StructureMapping sm : mappings)
288 if (sm.getPdbId().equals(pdbid))
297 * Import structure data and register a structure mapping for broadcasting
298 * colouring, mouseovers and selection events (convenience wrapper).
301 * - one or more sequences to be mapped to pdbFile
302 * @param targetChains
303 * - optional chain specification for mapping each sequence to pdb
304 * (may be nill, individual elements may be nill)
306 * - structure data resource
308 * - how to resolve data from resource
309 * @return null or the structure data parsed as a pdb file
311 synchronized public MCview.PDBfile setMapping(SequenceI[] sequence,
312 String[] targetChains, String pdbFile, String protocol)
314 return setMapping(true, sequence, targetChains, pdbFile, protocol);
318 * create sequence structure mappings between each sequence and the given
319 * pdbFile (retrieved via the given protocol).
321 * @param forStructureView
322 * when true, record the mapping for use in mouseOvers
325 * - one or more sequences to be mapped to pdbFile
326 * @param targetChains
327 * - optional chain specification for mapping each sequence to pdb
328 * (may be nill, individual elements may be nill)
330 * - structure data resource
332 * - how to resolve data from resource
333 * @return null or the structure data parsed as a pdb file
335 synchronized public MCview.PDBfile setMapping(boolean forStructureView,
336 SequenceI[] sequence,
337 String[] targetChains, String pdbFile, String protocol)
340 * There will be better ways of doing this in the future, for now we'll use
341 * the tried and tested MCview pdb mapping
343 MCview.PDBfile pdb = null;
344 boolean parseSecStr = processSecondaryStructure;
345 if (isPDBFileRegistered(pdbFile))
347 for (SequenceI sq : sequence)
350 while (ds.getDatasetSequence() != null)
352 ds = ds.getDatasetSequence();
355 if (ds.getAnnotation() != null)
357 for (AlignmentAnnotation ala : ds.getAnnotation())
359 // false if any annotation present from this structure
360 // JBPNote this fails for jmol/chimera view because the *file* is
361 // passed, not the structure data ID -
362 if (MCview.PDBfile.isCalcIdForFile(ala,
363 findIdForPDBFile(pdbFile)))
373 pdb = new MCview.PDBfile(addTempFacAnnot, parseSecStr,
374 secStructServices, pdbFile, protocol);
375 if (pdb.id != null && pdb.id.trim().length() > 0
376 && AppletFormatAdapter.FILE.equals(protocol))
378 registerPDBFile(pdb.id.trim(), pdbFile);
380 } catch (Exception ex)
382 ex.printStackTrace();
387 for (int s = 0; s < sequence.length; s++)
389 boolean infChain = true;
390 if (targetChains != null && targetChains[s] != null)
393 targetChain = targetChains[s];
395 else if (sequence[s].getName().indexOf("|") > -1)
397 targetChain = sequence[s].getName().substring(
398 sequence[s].getName().lastIndexOf("|") + 1);
399 if (targetChain.length() > 1)
401 if (targetChain.trim().length() == 0)
407 // not a valid chain identifier
418 AlignSeq maxAlignseq = null;
419 String maxChainId = " ";
420 PDBChain maxChain = null;
421 boolean first = true;
422 for (int i = 0; i < pdb.chains.size(); i++)
424 PDBChain chain = (pdb.chains.elementAt(i));
425 if (targetChain.length() > 0 && !targetChain.equals(chain.id)
428 continue; // don't try to map chains don't match.
430 // TODO: correctly determine sequence type for mixed na/peptide
432 AlignSeq as = new AlignSeq(sequence[s],
433 pdb.chains.elementAt(i).sequence,
434 pdb.chains.elementAt(i).isNa ? AlignSeq.DNA
436 as.calcScoreMatrix();
439 if (first || as.maxscore > max
440 || (as.maxscore == max && chain.id.equals(targetChain)))
446 maxChainId = chain.id;
449 if (maxChain == null)
453 final StringBuffer mappingDetails = new StringBuffer();
454 mappingDetails.append("\n\nPDB Sequence is :\nSequence = "
455 + maxChain.sequence.getSequenceAsString());
456 mappingDetails.append("\nNo of residues = "
457 + maxChain.residues.size() + "\n\n");
458 PrintStream ps = new PrintStream(System.out)
461 public void print(String x)
463 mappingDetails.append(x);
467 public void println()
469 mappingDetails.append("\n");
473 maxAlignseq.printAlignment(ps);
475 mappingDetails.append("\nPDB start/end " + maxAlignseq.seq2start
476 + " " + maxAlignseq.seq2end);
477 mappingDetails.append("\nSEQ start/end "
478 + (maxAlignseq.seq1start + sequence[s].getStart() - 1) + " "
479 + (maxAlignseq.seq1end + sequence[s].getEnd() - 1));
481 maxChain.makeExactMapping(maxAlignseq, sequence[s]);
482 jalview.datamodel.Mapping sqmpping = maxAlignseq
483 .getMappingFromS1(false);
484 jalview.datamodel.Mapping omap = new jalview.datamodel.Mapping(
485 sqmpping.getMap().getInverse());
486 maxChain.transferRESNUMFeatures(sequence[s], null);
488 // allocate enough slots to store the mapping from positions in
489 // sequence[s] to the associated chain
490 int[][] mapping = new int[sequence[s].findPosition(sequence[s]
491 .getLength()) + 2][2];
497 Atom tmp = (Atom) maxChain.atoms.elementAt(index);
498 if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
500 resNum = tmp.resNumber;
501 mapping[tmp.alignmentMapping + 1][0] = tmp.resNumber;
502 mapping[tmp.alignmentMapping + 1][1] = tmp.atomIndex;
506 } while (index < maxChain.atoms.size());
508 if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
510 pdbFile = "INLINE" + pdb.id;
512 StructureMapping newMapping = new StructureMapping(sequence[s],
513 pdbFile, pdb.id, maxChainId, mapping,
514 mappingDetails.toString());
515 if (forStructureView)
517 mappings.add(newMapping);
519 maxChain.transferResidueAnnotation(newMapping, sqmpping);
526 public void removeStructureViewerListener(Object svl, String[] pdbfiles)
528 listeners.removeElement(svl);
529 if (svl instanceof SequenceListener)
531 for (int i = 0; i < listeners.size(); i++)
533 if (listeners.elementAt(i) instanceof StructureListener)
535 ((StructureListener) listeners.elementAt(i))
536 .releaseReferences(svl);
541 if (pdbfiles == null)
546 Vector pdbs = new Vector();
547 for (int i = 0; i < pdbfiles.length; pdbs.addElement(pdbfiles[i++]))
551 StructureListener sl;
552 for (int i = 0; i < listeners.size(); i++)
554 if (listeners.elementAt(i) instanceof StructureListener)
556 sl = (StructureListener) listeners.elementAt(i);
557 handlepdbs = sl.getPdbFile();
558 for (int j = 0; j < handlepdbs.length; j++)
560 if (pdbs.contains(handlepdbs[j]))
562 pdbs.removeElement(handlepdbs[j]);
571 List<StructureMapping> tmp = new ArrayList<StructureMapping>();
572 for (StructureMapping sm : mappings)
574 if (!pdbs.contains(sm.pdbfile))
584 public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
586 if (listeners == null)
588 // old or prematurely sent event
591 SearchResults results = null;
592 SequenceI lastseq = null;
593 int lastipos = -1, indexpos;
594 for (int i = 0; i < listeners.size(); i++)
596 if (listeners.elementAt(i) instanceof SequenceListener)
600 results = new SearchResults();
602 for (StructureMapping sm : mappings)
604 if (sm.pdbfile.equals(pdbfile) && sm.pdbchain.equals(chain))
606 indexpos = sm.getSeqPos(pdbResNum);
607 if (lastipos != indexpos && lastseq != sm.sequence)
609 results.addResult(sm.sequence, indexpos, indexpos);
611 lastseq = sm.sequence;
612 // construct highlighted sequence list
613 for (AlignedCodonFrame acf : seqmappings)
615 acf.markMappedRegion(sm.sequence, indexpos, results);
624 for (int i = 0; i < listeners.size(); i++)
626 Object li = listeners.elementAt(i);
627 if (li instanceof SequenceListener)
629 ((SequenceListener) li).highlightSequence(results);
636 * highlight regions associated with a position (indexpos) in seq
639 * the sequence that the mouse over occurred on
641 * the absolute position being mouseovered in seq (0 to seq.length())
643 * the sequence position (if -1, seq.findPosition is called to
644 * resolve the residue number)
646 public void mouseOverSequence(SequenceI seq, int indexpos, int index,
649 boolean hasSequenceListeners = handlingVamsasMo
650 || !seqmappings.isEmpty();
651 SearchResults results = null;
654 index = seq.findPosition(indexpos);
656 for (int i = 0; i < listeners.size(); i++)
658 Object listener = listeners.elementAt(i);
659 if (listener == source)
661 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
662 // Temporary fudge with SequenceListener.getVamsasSource()
665 if (listener instanceof StructureListener)
667 highlightStructure((StructureListener) listener, seq, index);
671 if (listener instanceof SequenceListener)
673 final SequenceListener seqListener = (SequenceListener) listener;
674 if (hasSequenceListeners
675 && seqListener.getVamsasSource() != source)
677 if (relaySeqMappings)
681 results = MappingUtils.buildSearchResults(seq, index,
684 if (handlingVamsasMo)
686 results.addResult(seq, index, index);
689 seqListener.highlightSequence(results);
693 else if (listener instanceof VamsasListener && !handlingVamsasMo)
695 ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
698 else if (listener instanceof SecondaryStructureListener)
700 ((SecondaryStructureListener) listener).mouseOverSequence(seq,
708 * Send suitable messages to a StructureListener to highlight atoms
709 * corresponding to the given sequence position.
715 protected void highlightStructure(StructureListener sl, SequenceI seq,
719 List<AtomSpec> atoms = new ArrayList<AtomSpec>();
720 for (StructureMapping sm : mappings)
722 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
724 atomNo = sm.getAtomNum(index);
728 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
729 .getPDBResNum(index), atomNo));
733 sl.highlightAtoms(atoms);
737 * true if a mouse over event from an external (ie Vamsas) source is being
740 boolean handlingVamsasMo = false;
745 * as mouseOverSequence but only route event to SequenceListeners
749 * in an alignment sequence
751 public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
754 handlingVamsasMo = true;
755 long msg = sequenceI.hashCode() * (1 + position);
759 mouseOverSequence(sequenceI, position, -1, source);
761 handlingVamsasMo = false;
764 public Annotation[] colourSequenceFromStructure(SequenceI seq,
768 // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
769 // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
771 * Annotation [] annotations = new Annotation[seq.getLength()];
773 * StructureListener sl; int atomNo = 0; for (int i = 0; i <
774 * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
775 * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
777 * for (int j = 0; j < mappings.length; j++) {
779 * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
780 * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
781 * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
782 * "+mappings[j].pdbfile);
784 * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
785 * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
787 * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
788 * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
789 * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
790 * mappings[j].pdbfile); }
792 * annotations[index] = new Annotation("X",null,' ',0,col); } return
793 * annotations; } } } }
795 * return annotations;
799 public void structureSelectionChanged()
803 public void sequenceSelectionChanged()
807 public void sequenceColoursChanged(Object source)
809 StructureListener sl;
810 for (int i = 0; i < listeners.size(); i++)
812 if (listeners.elementAt(i) instanceof StructureListener)
814 sl = (StructureListener) listeners.elementAt(i);
815 sl.updateColours(source);
820 public StructureMapping[] getMapping(String pdbfile)
822 List<StructureMapping> tmp = new ArrayList<StructureMapping>();
823 for (StructureMapping sm : mappings)
825 if (sm.pdbfile.equals(pdbfile))
830 return tmp.toArray(new StructureMapping[tmp.size()]);
833 public String printMapping(String pdbfile)
835 StringBuilder sb = new StringBuilder(64);
836 for (StructureMapping sm : mappings)
838 if (sm.pdbfile.equals(pdbfile))
840 sb.append(sm.mappingDetails);
844 return sb.toString();
848 * Decrement the reference counter for each of the given mappings, and remove
849 * it entirely if its reference counter reduces to zero.
853 public void removeMappings(Set<AlignedCodonFrame> set)
857 for (AlignedCodonFrame acf : set)
865 * Decrement the reference counter for the given mapping, and remove it
866 * entirely if its reference counter reduces to zero.
870 public void removeMapping(AlignedCodonFrame acf)
872 if (acf != null && seqmappings.contains(acf))
874 int count = seqMappingRefCounts.get(acf);
878 seqMappingRefCounts.put(acf, count);
882 seqmappings.remove(acf);
883 seqMappingRefCounts.remove(acf);
889 * Add each of the given codonFrames to the stored set. If not aready present,
890 * increments its reference count instead.
894 public void addMappings(Set<AlignedCodonFrame> set)
898 for (AlignedCodonFrame acf : set)
906 * Add the given mapping to the stored set, or if already stored, increment
907 * its reference counter.
909 public void addMapping(AlignedCodonFrame acf)
913 if (seqmappings.contains(acf))
915 seqMappingRefCounts.put(acf, seqMappingRefCounts.get(acf) + 1);
919 seqmappings.add(acf);
920 seqMappingRefCounts.put(acf, 1);
925 public void addSelectionListener(SelectionListener selecter)
927 if (!sel_listeners.contains(selecter))
929 sel_listeners.add(selecter);
933 public void removeSelectionListener(SelectionListener toremove)
935 if (sel_listeners.contains(toremove))
937 sel_listeners.remove(toremove);
941 public synchronized void sendSelection(
942 jalview.datamodel.SequenceGroup selection,
943 jalview.datamodel.ColumnSelection colsel, SelectionSource source)
945 for (SelectionListener slis : sel_listeners)
949 slis.selection(selection, colsel, source);
954 Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
956 public synchronized void sendViewPosition(
957 jalview.api.AlignmentViewPanel source, int startRes, int endRes,
958 int startSeq, int endSeq)
961 if (view_listeners != null && view_listeners.size() > 0)
963 Enumeration<AlignmentViewPanelListener> listeners = view_listeners
965 while (listeners.hasMoreElements())
967 AlignmentViewPanelListener slis = listeners.nextElement();
970 slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
978 * release all references associated with this manager provider
982 public static void release(StructureSelectionManagerProvider jalviewLite)
984 // synchronized (instances)
986 if (instances == null)
990 StructureSelectionManager mnger = (instances.get(jalviewLite));
993 instances.remove(jalviewLite);
997 } catch (Throwable x)
1004 public void registerPDBEntry(PDBEntry pdbentry)
1006 if (pdbentry.getFile() != null
1007 && pdbentry.getFile().trim().length() > 0)
1009 registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1013 public void addCommandListener(CommandListener cl)
1015 if (!commandListeners.contains(cl))
1017 commandListeners.add(cl);
1021 public boolean hasCommandListener(CommandListener cl)
1023 return this.commandListeners.contains(cl);
1026 public boolean removeCommandListener(CommandListener l)
1028 return commandListeners.remove(l);
1032 * Forward a command to any command listeners (except for the command's
1036 * the command to be broadcast (in its form after being performed)
1038 * if true, the command was being 'undone'
1041 public void commandPerformed(CommandI command, boolean undo,
1042 VamsasSource source)
1044 for (CommandListener listener : commandListeners)
1046 listener.mirrorCommand(command, undo, this, source);
1051 * Returns a new CommandI representing the given command as mapped to the
1052 * given sequences. If no mapping could be made, or the command is not of a
1053 * mappable kind, returns null.
1061 public CommandI mapCommand(CommandI command, boolean undo,
1062 final AlignmentI mapTo, char gapChar)
1064 if (command instanceof EditCommand)
1066 return MappingUtils.mapEditCommand((EditCommand) command, undo,
1067 mapTo, gapChar, seqmappings);
1069 else if (command instanceof OrderCommand)
1071 return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1072 mapTo, seqmappings);