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.Arrays;
26 import java.util.Collections;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.IdentityHashMap;
31 import java.util.List;
32 import java.util.Locale;
35 import java.util.Vector;
37 import jalview.analysis.AlignSeq;
38 import jalview.api.StructureSelectionManagerProvider;
39 import jalview.bin.Console;
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.ContiguousI;
48 import jalview.datamodel.HiddenColumns;
49 import jalview.datamodel.PDBEntry;
50 import jalview.datamodel.SearchResults;
51 import jalview.datamodel.SearchResultsI;
52 import jalview.datamodel.SequenceI;
53 import jalview.ext.jmol.JmolParser;
54 import jalview.gui.IProgressIndicator;
55 import jalview.io.AppletFormatAdapter;
56 import jalview.io.DataSourceType;
57 import jalview.io.StructureFile;
58 import jalview.structure.StructureImportSettings.TFType;
59 import jalview.util.MapList;
60 import jalview.util.MappingUtils;
61 import jalview.util.MessageManager;
62 import jalview.util.Platform;
63 import jalview.ws.sifts.SiftsClient;
64 import jalview.ws.sifts.SiftsException;
65 import jalview.ws.sifts.SiftsSettings;
67 import mc_view.PDBChain;
68 import mc_view.PDBfile;
70 public class StructureSelectionManager
72 public final static String NEWLINE = System.lineSeparator();
74 static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
76 private List<StructureMapping> mappings = new ArrayList<>();
78 private boolean processSecondaryStructure = false;
80 private boolean secStructServices = false;
82 private boolean addTempFacAnnot = false;
85 * Set of any registered mappings between (dataset) sequences.
87 private List<AlignedCodonFrame> seqmappings = new ArrayList<>();
89 private List<CommandListener> commandListeners = new ArrayList<>();
91 private List<SelectionListener> sel_listeners = new ArrayList<>();
94 * @return true if will try to use external services for processing secondary
97 public boolean isSecStructServices()
99 return secStructServices;
103 * control use of external services for processing secondary structure
105 * @param secStructServices
107 public void setSecStructServices(boolean secStructServices)
109 this.secStructServices = secStructServices;
113 * flag controlling addition of any kind of structural annotation
115 * @return true if temperature factor annotation will be added
117 public boolean isAddTempFacAnnot()
119 return addTempFacAnnot;
123 * set flag controlling addition of structural annotation
125 * @param addTempFacAnnot
127 public void setAddTempFacAnnot(boolean addTempFacAnnot)
129 this.addTempFacAnnot = addTempFacAnnot;
134 * @return if true, the structure manager will attempt to add secondary
135 * structure lines for unannotated sequences
138 public boolean isProcessSecondaryStructure()
140 return processSecondaryStructure;
144 * Control whether structure manager will try to annotate mapped sequences
145 * with secondary structure from PDB data.
149 public void setProcessSecondaryStructure(boolean enable)
151 processSecondaryStructure = enable;
155 * debug function - write all mappings to stdout
157 public void reportMapping()
159 if (mappings.isEmpty())
162 .errPrintln("reportMapping: No PDB/Sequence mappings.");
166 jalview.bin.Console.errPrintln(
167 "reportMapping: There are " + mappings.size() + " mappings.");
169 for (StructureMapping sm : mappings)
172 .errPrintln("mapping " + i++ + " : " + sm.pdbfile);
178 * map between the PDB IDs (or structure identifiers) used by Jalview and the
179 * absolute filenames for PDB data that corresponds to it
181 Map<String, String> pdbIdFileName = new HashMap<>();
183 Map<String, String> pdbFileNameId = new HashMap<>();
185 public void registerPDBFile(String idForFile, String absoluteFile)
187 pdbIdFileName.put(idForFile, absoluteFile);
188 pdbFileNameId.put(absoluteFile, idForFile);
191 public String findIdForPDBFile(String idOrFile)
193 String id = pdbFileNameId.get(idOrFile);
197 public String findFileForPDBId(String idOrFile)
199 String id = pdbIdFileName.get(idOrFile);
203 public boolean isPDBFileRegistered(String idOrFile)
205 return pdbFileNameId.containsKey(idOrFile)
206 || pdbIdFileName.containsKey(idOrFile);
209 private static StructureSelectionManager nullProvider = null;
211 public static StructureSelectionManager getStructureSelectionManager(
212 StructureSelectionManagerProvider context)
216 if (nullProvider == null)
218 if (instances != null)
220 throw new Error(MessageManager.getString(
221 "error.implementation_error_structure_selection_manager_null"),
222 new NullPointerException(MessageManager
223 .getString("exception.ssm_context_is_null")));
227 nullProvider = new StructureSelectionManager();
232 if (instances == null)
234 instances = new java.util.IdentityHashMap<>();
236 StructureSelectionManager instance = instances.get(context);
237 if (instance == null)
239 if (nullProvider != null)
241 instance = nullProvider;
245 instance = new StructureSelectionManager();
247 instances.put(context, instance);
253 * flag controlling whether SeqMappings are relayed from received sequence
254 * mouse over events to other sequences
256 boolean relaySeqMappings = true;
259 * Enable or disable relay of seqMapping events to other sequences. You might
260 * want to do this if there are many sequence mappings and the host computer
265 public void setRelaySeqMappings(boolean relay)
267 relaySeqMappings = relay;
271 * get the state of the relay seqMappings flag.
273 * @return true if sequence mouse overs are being relayed to other mapped
276 public boolean isRelaySeqMappingsEnabled()
278 return relaySeqMappings;
281 Vector listeners = new Vector();
284 * register a listener for alignment sequence mouseover events
288 public void addStructureViewerListener(Object svl)
290 if (!listeners.contains(svl))
292 listeners.addElement(svl);
297 * Returns the filename the PDB id is already mapped to if known, or null if
303 public String alreadyMappedToFile(String pdbid)
305 for (StructureMapping sm : mappings)
307 if (sm.getPdbId().equalsIgnoreCase(pdbid))
316 * Import structure data and register a structure mapping for broadcasting
317 * colouring, mouseovers and selection events (convenience wrapper).
320 * - one or more sequences to be mapped to pdbFile
321 * @param targetChains
322 * - optional chain specification for mapping each sequence to pdb
323 * (may be nill, individual elements may be nill)
325 * - structure data resource
327 * - how to resolve data from resource
328 * @return null or the structure data parsed as a pdb file
330 synchronized public StructureFile setMapping(SequenceI[] sequence,
331 String[] targetChains, String pdbFile, DataSourceType protocol,
332 IProgressIndicator progress)
334 return computeMapping(true, sequence, targetChains, pdbFile, protocol,
335 progress, null, null, true);
339 * Import a single structure file and register sequence structure mappings for
340 * broadcasting colouring, mouseovers and selection events (convenience
343 * @param forStructureView
344 * when true, record the mapping for use in mouseOvers
346 * - one or more sequences to be mapped to pdbFile
347 * @param targetChains
348 * - optional chain specification for mapping each sequence to pdb
349 * (may be nill, individual elements may be nill)
351 * - structure data resource
353 * - how to resolve data from resource
354 * @return null or the structure data parsed as a pdb file
356 synchronized public StructureFile setMapping(boolean forStructureView,
357 SequenceI[] sequenceArray, String[] targetChainIds,
358 String pdbFile, DataSourceType sourceType, TFType tft,
361 return setMapping(forStructureView, sequenceArray, targetChainIds,
362 pdbFile, sourceType, tft, paeFilename, true);
366 * create sequence structure mappings between each sequence and the given
367 * pdbFile (retrieved via the given protocol). Either constructs a mapping
368 * using NW alignment or derives one from any available SIFTS mapping data.
370 * @param forStructureView
371 * when true, record the mapping for use in mouseOvers
373 * @param sequenceArray
374 * - one or more sequences to be mapped to pdbFile
375 * @param targetChainIds
376 * - optional chain specification for mapping each sequence to pdb
377 * (may be nill, individual elements may be nill) - JBPNote: JAL-2693
378 * - this should be List<List<String>>, empty lists indicate no
379 * predefined mappings
381 * - structure data resource
383 * - how to resolve data from resource
385 * - specify how to interpret the temperature factor column in the
388 * - when not null, specifies a filename containing a matrix
389 * formatted in JSON using one of the known PAE formats
390 * @param doXferSettings
391 * - when true, transfer annotation to mapped sequences in
393 * @return null or the structure data parsed as a pdb file
395 synchronized public StructureFile setMapping(boolean forStructureView,
396 SequenceI[] sequenceArray, String[] targetChainIds,
397 String pdbFile, DataSourceType sourceType, TFType tft,
398 String paeFilename, boolean doXferSettings)
400 return computeMapping(forStructureView, sequenceArray, targetChainIds,
401 pdbFile, sourceType, null, tft, paeFilename, doXferSettings);
405 * create sequence structure mappings between each sequence and the given
406 * pdbFile (retrieved via the given protocol). Either constructs a mapping
407 * using NW alignment or derives one from any available SIFTS mapping data.
409 * @param forStructureView
410 * when true, record the mapping for use in mouseOvers
412 * @param sequenceArray
413 * - one or more sequences to be mapped to pdbFile
414 * @param targetChainIds
415 * - optional chain specification for mapping each sequence to pdb
416 * (may be nill, individual elements may be nill) - JBPNote: JAL-2693
417 * - this should be List<List<String>>, empty lists indicate no
418 * predefined mappings
420 * - structure data resource
422 * - how to resolve data from resource
423 * @param IProgressIndicator
424 * reference to UI component that maintains a progress bar for the
427 * - specify how to interpret the temperature factor column in the
430 * - when not null, specifies a filename containing a matrix
431 * formatted in JSON using one of the known PAE formats
432 * @param doXferSettings
433 * - when true, transfer annotation to mapped sequences in
435 * @return null or the structure data parsed as a pdb file
437 synchronized public StructureFile computeMapping(boolean forStructureView,
438 SequenceI[] sequenceArray, String[] targetChainIds,
439 String pdbFile, DataSourceType sourceType,
440 IProgressIndicator progress, TFType tft, String paeFilename,
441 boolean doXferSettings)
443 long progressSessionId = System.currentTimeMillis() * 3;
446 * do we extract and transfer annotation from 3D data ?
448 // FIXME: possibly should just delete
450 boolean parseSecStr = processSecondaryStructure
451 && !isStructureFileProcessed(pdbFile, sequenceArray);
453 StructureFile pdb = null;
454 boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
457 // FIXME if sourceType is not null, we've lost data here
458 sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
459 pdb = new JmolParser(false, pdbFile, sourceType);
460 if (paeFilename != null)
462 pdb.setPAEMatrix(paeFilename);
464 pdb.setTemperatureFactorType(tft);
465 pdb.addSettings(parseSecStr && processSecondaryStructure,
466 parseSecStr && addTempFacAnnot,
467 parseSecStr && secStructServices);
468 // save doXferSettings and reset after doParse()
469 boolean temp = pdb.getDoXferSettings();
470 pdb.setDoXferSettings(doXferSettings);
472 pdb.setDoXferSettings(temp);
473 if (pdb.getId() != null && pdb.getId().trim().length() > 0
474 && DataSourceType.FILE == sourceType)
476 registerPDBFile(pdb.getId().trim(), pdbFile);
478 // if PDBId is unavailable then skip SIFTS mapping execution path
479 // TODO: JAL-3868 need to know if structure is actually from
480 // PDB (has valid PDB ID and has provenance suggesting it
481 // actually came from PDB)
482 boolean isProtein = false;
483 for (SequenceI s : sequenceArray)
491 isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable()
492 && !pdb.getId().startsWith("AF-") && isProtein;
494 } catch (Exception ex)
496 ex.printStackTrace();
500 * sifts client - non null if SIFTS mappings are to be used
502 SiftsClient siftsClient = null;
507 siftsClient = new SiftsClient(pdb);
509 } catch (SiftsException e)
511 isMapUsingSIFTs = false;
512 Console.error("SIFTS mapping failed", e);
513 Console.error("Falling back on Needleman & Wunsch alignment");
517 String targetChainId;
518 for (int s = 0; s < sequenceArray.length; s++)
520 boolean infChain = true;
521 final SequenceI seq = sequenceArray[s];
523 while (ds.getDatasetSequence() != null)
525 ds = ds.getDatasetSequence();
527 List <PDBEntry> putativePDBe = PDBEntryUtils.selectPutativePDBe(seq,ds, pdb);
529 if (targetChainIds != null && targetChainIds[s] != null)
532 targetChainId = targetChainIds[s];
535 targetChainId = PDBEntryUtils.inferChainId(seq);
539 * Attempt pairwise alignment of the sequence with each chain in the PDB,
540 * and remember the highest scoring chain
543 AlignSeq maxAlignseq = null;
544 String maxChainId = " ";
545 PDBChain maxChain = null;
546 boolean first = true;
547 PDBChain idLengthChain = null;
548 for (PDBChain chain : pdb.getChains())
550 if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
553 continue; // don't try to map chains don't match.
555 PDBEntry putativeChain = null;
556 if (!putativePDBe.isEmpty() && (putativeChain = PDBEntryUtils
557 .selectPutativePDBEntry(putativePDBe, chain)) == null)
561 // TODO: correctly determine sequence type for mixed na/peptide
563 final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
564 AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
566 // TODO: JAL-4366 determinine of a crummy alignment but exact match should make this chain the one to be mapped to a 3di sequence
567 if (as.s1str.length() == as.s2str.length())
569 idLengthChain = chain;
572 if (first || as.maxscore > max
573 || (as.maxscore == max && chain.id.equals(targetChainId)))
579 maxChainId = chain.id;
582 if (maxChain == null)
586 if (sourceType == DataSourceType.PASTE)
588 pdbFile = "INLINE" + pdb.getId();
590 List<StructureMapping> seqToStrucMapping = new ArrayList<>();
592 List<StructureMapping> foundSiftsMappings = new ArrayList<>();
593 if (isMapUsingSIFTs && seq.isProtein())
595 if (progress != null)
597 progress.setProgressBar(
599 .getString("status.obtaining_mapping_with_sifts"),
602 jalview.datamodel.Mapping sqmpping = maxAlignseq
603 .getMappingFromS1(false);
604 if (targetChainId != null && !targetChainId.trim().isEmpty())
606 StructureMapping siftsMapping;
609 siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
610 pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
611 seqToStrucMapping.add(siftsMapping);
612 maxChain.makeExactMapping(siftsMapping, seq);
613 maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS",
614 pdb.getId().toLowerCase(Locale.ROOT));
615 maxChain.transferResidueAnnotation(siftsMapping, null);
616 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
617 foundSiftsMappings.add(siftsMapping);
619 } catch (SiftsException e)
621 Console.error(e.getMessage());
626 for (PDBChain chain : pdb.getChains())
628 StructureMapping siftsMapping = null;
631 siftsMapping = getStructureMapping(seq, pdbFile, chain.id,
632 pdb, chain, sqmpping, maxAlignseq, siftsClient);
633 foundSiftsMappings.add(siftsMapping);
634 chain.makeExactMapping(siftsMapping, seq);
635 chain.transferRESNUMFeatures(seq, "IEA: SIFTS",
636 pdb.getId().toLowerCase(Locale.ROOT));// FIXME: is this
638 chain.transferResidueAnnotation(siftsMapping, null);
639 } catch (SiftsException e)
641 jalview.bin.Console.errPrintln(e.getMessage());
642 } catch (Exception e)
644 jalview.bin.Console.errPrintln(
645 "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair");
646 jalview.bin.Console.errPrintln(e.getMessage());
649 // If sifts was successful, add mappings and return
650 if (!foundSiftsMappings.isEmpty())
652 ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
656 // If sifts was successful, add mappings and return
657 if (!foundSiftsMappings.isEmpty())
659 seqToStrucMapping.addAll(foundSiftsMappings);
662 if (foundSiftsMappings.isEmpty())
664 // Not doing SIFTS, or SIFTS failed for some reason.
666 // first check if we should use an identity mapping
667 if (idLengthChain != null && maxAlignseq.getS2Coverage() < 0.75)
670 "Assuming 3Dsi identity mapping between structure and sequence");
671 StructureMapping matchMapping = getIdMappings(seq, pdbFile,
672 idLengthChain.id, idLengthChain, pdb);
673 seqToStrucMapping.add(matchMapping);
674 ds.addPDBId(idLengthChain.sequence.getAllPDBEntries().get(0));
675 Console.info("Mapping added.");
679 if (maxAlignseq.getS1Coverage()<0.15 && maxAlignseq.getS2Coverage()<0.15)
681 // skip this - the NW alignment is spurious
684 // Construct a needleman wunsch mapping instead.
685 if (progress != null)
687 progress.setProgressBar(
688 MessageManager.getString(
689 "status.obtaining_mapping_with_nw_alignment"),
692 StructureMapping nwMapping = getNWMappings(seq, pdbFile,
693 maxChainId, maxChain, pdb, maxAlignseq);
694 seqToStrucMapping.add(nwMapping);
695 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
698 if (forStructureView)
700 for (StructureMapping sm : seqToStrucMapping)
702 addStructureMapping(sm); // not addAll!
705 if (progress != null)
707 progress.setProgressBar(null, progressSessionId);
714 * check if we need to extract secondary structure from given pdbFile and
715 * transfer to sequences
718 * @param sequenceArray
721 private boolean isStructureFileProcessed(String pdbFile,
722 SequenceI[] sequenceArray)
724 boolean processed = false;
725 if (isPDBFileRegistered(pdbFile))
727 for (SequenceI sq : sequenceArray)
730 while (ds.getDatasetSequence() != null)
732 ds = ds.getDatasetSequence();
735 if (ds.getAnnotation() != null)
737 for (AlignmentAnnotation ala : ds.getAnnotation())
739 // false if any annotation present from this structure
740 // JBPNote this fails for jmol/chimera view because the *file* is
741 // passed, not the structure data ID -
742 if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
753 public void addStructureMapping(StructureMapping sm)
755 if (!mappings.contains(sm))
762 * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
767 * @param targetChainId
773 * client for retrieval of SIFTS mappings for this structure
775 * @throws SiftsException
777 private StructureMapping getStructureMapping(SequenceI seq,
778 String pdbFile, String targetChainId, StructureFile pdb,
779 PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
780 AlignSeq maxAlignseq, SiftsClient siftsClient)
781 throws SiftsException
783 StructureMapping curChainMapping = siftsClient
784 .getSiftsStructureMapping(seq, pdbFile, targetChainId);
787 PDBChain chain = pdb.findChain(targetChainId);
790 chain.transferResidueAnnotation(curChainMapping, null);
792 } catch (Exception e)
796 return curChainMapping;
800 * construct a mapping based on a pairwise alignment of the sequence and chain
810 private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
811 String maxChainId, PDBChain maxChain, StructureFile pdb,
812 AlignSeq maxAlignseq)
814 final StringBuilder mappingDetails = new StringBuilder(128);
815 mappingDetails.append(NEWLINE)
816 .append("Sequence \u27f7 Structure mapping details");
817 mappingDetails.append(NEWLINE);
819 .append("Method: inferred with Needleman & Wunsch alignment");
820 mappingDetails.append(NEWLINE).append("PDB Sequence is :")
821 .append(NEWLINE).append("Sequence = ")
822 .append(maxChain.sequence.getSequenceAsString());
823 mappingDetails.append(NEWLINE).append("No of residues = ")
824 .append(maxChain.residues.size()).append(NEWLINE)
826 PrintStream ps = new PrintStream(System.out)
829 public void print(String x)
831 mappingDetails.append(x);
835 public void println()
837 mappingDetails.append(NEWLINE);
841 maxAlignseq.printAlignment(ps);
843 mappingDetails.append(NEWLINE).append("PDB start/end ");
844 mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
846 mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
847 mappingDetails.append(NEWLINE).append("SEQ start/end ");
850 .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
852 mappingDetails.append(
853 String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
854 mappingDetails.append(NEWLINE);
855 maxChain.makeExactMapping(maxAlignseq, seq);
856 jalview.datamodel.Mapping sqmpping = maxAlignseq
857 .getMappingFromS1(false);
858 maxChain.transferRESNUMFeatures(seq, null,
859 pdb.getId().toLowerCase(Locale.ROOT));
861 HashMap<Integer, int[]> mapping = new HashMap<>();
868 Atom tmp = maxChain.atoms.elementAt(index);
869 if ((resNum != tmp.resNumber || insCode != tmp.insCode)
870 && tmp.alignmentMapping != -1)
872 resNum = tmp.resNumber;
873 insCode = tmp.insCode;
874 if (tmp.alignmentMapping >= -1)
876 mapping.put(tmp.alignmentMapping + 1,
878 { tmp.resNumber, tmp.atomIndex });
883 } while (index < maxChain.atoms.size());
885 StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
886 pdb.getId(), maxChainId, mapping, mappingDetails.toString());
887 maxChain.transferResidueAnnotation(nwMapping, sqmpping);
892 * construct a 1:1 mapping using given residue and sequence numbering
895 * @param identityChainId
896 * @param identityChain
901 private StructureMapping getIdMappings(SequenceI seq, String pdbFile,
902 String identityChainId, PDBChain identityChain, StructureFile pdb)
904 final StringBuilder mappingDetails = new StringBuilder(128);
905 mappingDetails.append(NEWLINE)
906 .append("Sequence \u27f7 Structure mapping details");
907 mappingDetails.append(NEWLINE);
908 mappingDetails.append("Method: Matching length 1:1");
909 mappingDetails.append(NEWLINE).append("PDB Sequence is :")
910 .append(NEWLINE).append("Sequence = ")
911 .append(identityChain.sequence.getSequenceAsString());
912 mappingDetails.append(NEWLINE).append("No of residues = ")
913 .append(identityChain.residues.size()).append(NEWLINE)
916 mappingDetails.append(NEWLINE)
917 .append("Aligned Sequence is: " + seq.getDisplayId(true));
918 mappingDetails.append(NEWLINE)
919 .append("Sequence = " + seq.getSequenceAsString());
921 int from = Math.max(seq.getStart(),identityChain.sequence.getStart());
922 int to = Math.min(seq.getEnd(), identityChain.sequence.getEnd());
923 jalview.datamodel.Mapping sqmpping = new jalview.datamodel.Mapping(seq,
924 new MapList(new int[]
929 identityChain.mapChainWith(sqmpping, seq);
931 identityChain.transferRESNUMFeatures(seq, null,
932 pdb.getId().toLowerCase(Locale.ROOT));
935 // TODO REFACTOR TO PDBChain as a builder
936 HashMap<Integer, int[]> mapping = new HashMap<>();
943 Atom tmp = identityChain.atoms.elementAt(index);
944 if ((resNum != tmp.resNumber || insCode != tmp.insCode)
945 && tmp.alignmentMapping != -1)
947 resNum = tmp.resNumber;
948 insCode = tmp.insCode;
949 if (tmp.alignmentMapping >= -1)
951 mapping.put(tmp.alignmentMapping + 1,
953 { tmp.resNumber, tmp.atomIndex });
958 } while (index < identityChain.atoms.size());
960 StructureMapping idMapping = new StructureMapping(seq, pdbFile,
961 pdb.getId(), identityChainId, mapping,
962 mappingDetails.toString());
963 identityChain.transferResidueAnnotation(idMapping, sqmpping);
967 public void removeStructureViewerListener(Object svl, String[] pdbfiles)
969 listeners.removeElement(svl);
970 if (svl instanceof SequenceListener)
972 for (int i = 0; i < listeners.size(); i++)
974 if (listeners.elementAt(i) instanceof StructureListener)
976 ((StructureListener) listeners.elementAt(i))
977 .releaseReferences(svl);
982 if (pdbfiles == null)
988 * Remove mappings to the closed listener's PDB files, but first check if
989 * another listener is still interested
991 List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
993 StructureListener sl;
994 for (int i = 0; i < listeners.size(); i++)
996 if (listeners.elementAt(i) instanceof StructureListener)
998 sl = (StructureListener) listeners.elementAt(i);
999 for (String pdbfile : sl.getStructureFiles())
1001 pdbs.remove(pdbfile);
1007 * Rebuild the mappings set, retaining only those which are for 'other' PDB
1010 if (pdbs.size() > 0)
1012 List<StructureMapping> tmp = new ArrayList<>();
1013 for (StructureMapping sm : mappings)
1015 if (!pdbs.contains(sm.pdbfile))
1026 * hack to highlight a range of positions at once on any structure views
1028 * @param sequenceRef
1030 * - series of int start-end ranges as positions on sequenceRef
1034 public void highlightPositionsOn(SequenceI sequenceRef, int[][] is,
1037 boolean hasSequenceListeners = handlingVamsasMo
1038 || !seqmappings.isEmpty();
1039 SearchResultsI results = null;
1040 ArrayList<Integer> listOfPositions = new ArrayList<Integer>();
1041 for (int[] s_e : is)
1043 for (int p = s_e[0]; p <= s_e[1]; listOfPositions.add(p++))
1046 int seqpos[] = new int[listOfPositions.size()];
1048 for (Integer p : listOfPositions)
1053 for (i = 0; i < listeners.size(); i++)
1055 Object listener = listeners.elementAt(i);
1056 if (listener == source)
1058 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1059 // Temporary fudge with SequenceListener.getVamsasSource()
1062 if (listener instanceof StructureListener)
1064 highlightStructure((StructureListener) listener, sequenceRef,
1072 * Propagate mouseover of a single position in a structure
1079 public String mouseOverStructure(int pdbResNum, String chain,
1082 AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
1083 List<AtomSpec> atoms = Collections.singletonList(atomSpec);
1084 return mouseOverStructure(atoms);
1088 * Propagate mouseover or selection of multiple positions in a structure
1092 public String mouseOverStructure(List<AtomSpec> atoms)
1094 if (listeners == null)
1096 // old or prematurely sent event
1099 boolean hasSequenceListener = false;
1100 for (int i = 0; i < listeners.size(); i++)
1102 if (listeners.elementAt(i) instanceof SequenceListener)
1104 hasSequenceListener = true;
1107 if (!hasSequenceListener)
1112 SearchResultsI results = findAlignmentPositionsForStructurePositions(
1114 String result = null;
1115 for (Object li : listeners)
1117 if (li instanceof SequenceListener)
1119 String s = ((SequenceListener) li).highlightSequence(results);
1130 * Constructs a SearchResults object holding regions (if any) in the Jalview
1131 * alignment which have a mapping to the structure viewer positions in the
1137 public SearchResultsI findAlignmentPositionsForStructurePositions(
1138 List<AtomSpec> atoms)
1140 SearchResultsI results = new SearchResults();
1141 for (AtomSpec atom : atoms)
1143 SequenceI lastseq = null;
1145 for (StructureMapping sm : mappings)
1147 if (sm.pdbfile.equals(atom.getPdbFile())
1148 && sm.pdbchain.equals(atom.getChain()))
1150 int indexpos = sm.getSeqPos(atom.getPdbResNum());
1151 if (lastipos != indexpos || lastseq != sm.sequence)
1153 results.appendResult(sm.sequence, indexpos, indexpos);
1154 lastipos = indexpos;
1155 lastseq = sm.sequence;
1156 // construct highlighted sequence list
1157 for (AlignedCodonFrame acf : seqmappings)
1159 acf.markMappedRegion(sm.sequence, indexpos, results);
1169 * highlight regions associated with a position (indexpos) in seq
1172 * the sequence that the mouse over occurred on
1174 * the absolute position being mouseovered in seq (0 to seq.length())
1176 * the sequence position (if -1, seq.findPosition is called to
1177 * resolve the residue number)
1179 public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
1180 VamsasSource source)
1182 boolean hasSequenceListeners = handlingVamsasMo
1183 || !seqmappings.isEmpty();
1184 SearchResultsI results = null;
1187 seqPos = seq.findPosition(indexpos);
1189 for (int i = 0; i < listeners.size(); i++)
1191 Object listener = listeners.elementAt(i);
1192 if (listener == source)
1194 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1195 // Temporary fudge with SequenceListener.getVamsasSource()
1198 if (listener instanceof StructureListener)
1200 highlightStructure((StructureListener) listener, seq, seqPos);
1204 if (listener instanceof SequenceListener)
1206 final SequenceListener seqListener = (SequenceListener) listener;
1207 if (hasSequenceListeners
1208 && seqListener.getVamsasSource() != source)
1210 if (relaySeqMappings)
1212 if (results == null)
1214 results = MappingUtils.buildSearchResults(seq, seqPos,
1217 if (handlingVamsasMo)
1219 results.addResult(seq, seqPos, seqPos);
1222 if (!results.isEmpty())
1224 seqListener.highlightSequence(results);
1229 else if (listener instanceof VamsasListener && !handlingVamsasMo)
1231 ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
1234 else if (listener instanceof SecondaryStructureListener)
1236 ((SecondaryStructureListener) listener).mouseOverSequence(seq,
1244 * Send suitable messages to a StructureListener to highlight atoms
1245 * corresponding to the given sequence position(s)
1251 public void highlightStructure(StructureListener sl, SequenceI seq,
1254 if (!sl.isListeningFor(seq))
1259 List<AtomSpec> atoms = new ArrayList<>();
1260 for (StructureMapping sm : mappings)
1262 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1263 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1264 .getDatasetSequence() == seq.getDatasetSequence()))
1266 for (int index : positions)
1268 atomNo = sm.getAtomNum(index);
1272 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1273 sm.getPDBResNum(index), atomNo));
1278 sl.highlightAtoms(atoms);
1281 public void highlightStructureRegionsFor(StructureListener sl,
1282 SequenceI[] seqs, int... columns)
1284 List<SequenceI> to_highlight = new ArrayList<SequenceI>();
1285 for (SequenceI seq : seqs)
1287 if (sl.isListeningFor(seq))
1289 to_highlight.add(seq);
1292 if (to_highlight.size() == 0)
1296 List<AtomSpec> atoms = new ArrayList<>();
1297 for (SequenceI seq : to_highlight)
1300 for (StructureMapping sm : mappings)
1302 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1303 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1304 .getDatasetSequence() == seq.getDatasetSequence()))
1307 for (int i = 0; i < columns.length; i += 2)
1309 ContiguousI positions = seq.findPositions(columns[i] + 1,
1310 columns[i + 1] + 1);
1311 if (positions == null)
1315 for (int index = positions.getBegin(); index <= positions
1319 atomNo = sm.getAtomNum(index);
1323 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1324 sm.getPDBResNum(index), atomNo));
1330 if (atoms.size() > 0)
1332 sl.highlightAtoms(atoms);
1338 * true if a mouse over event from an external (ie Vamsas) source is being
1341 boolean handlingVamsasMo = false;
1346 * as mouseOverSequence but only route event to SequenceListeners
1350 * in an alignment sequence
1352 public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
1353 VamsasSource source)
1355 handlingVamsasMo = true;
1356 long msg = sequenceI.hashCode() * (1 + position);
1360 mouseOverSequence(sequenceI, position, -1, source);
1362 handlingVamsasMo = false;
1365 public Annotation[] colourSequenceFromStructure(SequenceI seq,
1369 // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
1370 // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
1372 * Annotation [] annotations = new Annotation[seq.getLength()];
1374 * StructureListener sl; int atomNo = 0; for (int i = 0; i <
1375 * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
1376 * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
1378 * for (int j = 0; j < mappings.length; j++) {
1380 * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
1381 * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
1382 * jalview.bin.Console.outPrintln(pdbid+" "+mappings[j].getPdbId() +"
1383 * "+mappings[j].pdbfile);
1385 * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
1386 * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
1388 * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
1389 * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
1390 * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
1391 * mappings[j].pdbfile); }
1393 * annotations[index] = new Annotation("X",null,' ',0,col); } return
1394 * annotations; } } } }
1396 * return annotations;
1400 public void structureSelectionChanged()
1404 public void sequenceSelectionChanged()
1408 public void sequenceColoursChanged(Object source)
1410 StructureListener sl;
1411 for (int i = 0; i < listeners.size(); i++)
1413 if (listeners.elementAt(i) instanceof StructureListener)
1415 sl = (StructureListener) listeners.elementAt(i);
1416 sl.updateColours(source);
1421 public StructureMapping[] getMapping(String pdbfile)
1423 List<StructureMapping> tmp = new ArrayList<>();
1424 for (StructureMapping sm : mappings)
1426 if (sm.pdbfile.equals(pdbfile))
1431 return tmp.toArray(new StructureMapping[tmp.size()]);
1435 * Returns a readable description of all mappings for the given pdbfile to any
1436 * of the given sequences
1442 public String printMappings(String pdbfile, List<SequenceI> seqs)
1444 if (pdbfile == null || seqs == null || seqs.isEmpty())
1449 StringBuilder sb = new StringBuilder(64);
1450 for (StructureMapping sm : mappings)
1452 if (Platform.pathEquals(sm.pdbfile, pdbfile)
1453 && seqs.contains(sm.sequence))
1455 sb.append(sm.mappingDetails);
1457 // separator makes it easier to read multiple mappings
1458 sb.append("=====================");
1464 return sb.toString();
1468 * Remove the given mapping
1472 public void deregisterMapping(AlignedCodonFrame acf)
1476 boolean removed = seqmappings.remove(acf);
1477 if (removed && seqmappings.isEmpty())
1479 jalview.bin.Console.outPrintln("All mappings removed");
1485 * Add each of the given codonFrames to the stored set, if not aready present.
1489 public void registerMappings(List<AlignedCodonFrame> mappings)
1491 if (mappings != null)
1493 for (AlignedCodonFrame acf : mappings)
1495 registerMapping(acf);
1501 * Add the given mapping to the stored set, unless already stored.
1503 public void registerMapping(AlignedCodonFrame acf)
1507 if (!seqmappings.contains(acf))
1509 seqmappings.add(acf);
1515 * Resets this object to its initial state by removing all registered
1516 * listeners, codon mappings, PDB file mappings
1518 public void resetAll()
1520 if (mappings != null)
1524 if (seqmappings != null)
1526 seqmappings.clear();
1528 if (sel_listeners != null)
1530 sel_listeners.clear();
1532 if (listeners != null)
1536 if (commandListeners != null)
1538 commandListeners.clear();
1540 if (view_listeners != null)
1542 view_listeners.clear();
1544 if (pdbFileNameId != null)
1546 pdbFileNameId.clear();
1548 if (pdbIdFileName != null)
1550 pdbIdFileName.clear();
1554 public void addSelectionListener(SelectionListener selecter)
1556 if (!sel_listeners.contains(selecter))
1558 sel_listeners.add(selecter);
1562 public void removeSelectionListener(SelectionListener toremove)
1564 if (sel_listeners.contains(toremove))
1566 sel_listeners.remove(toremove);
1570 public synchronized void sendSelection(
1571 jalview.datamodel.SequenceGroup selection,
1572 jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
1573 SelectionSource source)
1575 for (SelectionListener slis : sel_listeners)
1579 slis.selection(selection, colsel, hidden, source);
1584 Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
1586 public synchronized void sendViewPosition(
1587 jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1588 int startSeq, int endSeq)
1591 if (view_listeners != null && view_listeners.size() > 0)
1593 Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1595 while (listeners.hasMoreElements())
1597 AlignmentViewPanelListener slis = listeners.nextElement();
1600 slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1608 * release all references associated with this manager provider
1610 * @param jalviewLite
1612 public static void release(StructureSelectionManagerProvider jalviewLite)
1614 // synchronized (instances)
1616 if (instances == null)
1620 StructureSelectionManager mnger = (instances.get(jalviewLite));
1623 instances.remove(jalviewLite);
1626 /* bsoares 2019-03-20 finalize deprecated, no apparent external
1627 * resources to close
1629 // mnger.finalize();
1630 } catch (Throwable x)
1637 public void registerPDBEntry(PDBEntry pdbentry)
1639 if (pdbentry.getFile() != null
1640 && pdbentry.getFile().trim().length() > 0)
1642 registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1646 public void addCommandListener(CommandListener cl)
1648 if (!commandListeners.contains(cl))
1650 commandListeners.add(cl);
1654 public boolean hasCommandListener(CommandListener cl)
1656 return this.commandListeners.contains(cl);
1659 public boolean removeCommandListener(CommandListener l)
1661 return commandListeners.remove(l);
1665 * Forward a command to any command listeners (except for the command's
1669 * the command to be broadcast (in its form after being performed)
1671 * if true, the command was being 'undone'
1674 public void commandPerformed(CommandI command, boolean undo,
1675 VamsasSource source)
1677 for (CommandListener listener : commandListeners)
1679 listener.mirrorCommand(command, undo, this, source);
1684 * Returns a new CommandI representing the given command as mapped to the
1685 * given sequences. If no mapping could be made, or the command is not of a
1686 * mappable kind, returns null.
1694 public CommandI mapCommand(CommandI command, boolean undo,
1695 final AlignmentI mapTo, char gapChar)
1697 if (command instanceof EditCommand)
1699 return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
1700 gapChar, seqmappings);
1702 else if (command instanceof OrderCommand)
1704 return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1705 mapTo, seqmappings);
1710 public List<AlignedCodonFrame> getSequenceMappings()
1716 * quick and dirty route to just highlight all structure positions for a range
1719 * @param sequencesArray
1721 * start-end columns on sequencesArray
1723 * origin parent AlignmentPanel
1725 public void highlightPositionsOnMany(SequenceI[] sequencesArray, int[] is,
1728 for (int i = 0; i < listeners.size(); i++)
1730 Object listener = listeners.elementAt(i);
1731 if (listener == source)
1733 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1734 // Temporary fudge with SequenceListener.getVamsasSource()
1737 if (listener instanceof StructureListener)
1739 highlightStructureRegionsFor((StructureListener) listener,
1740 sequencesArray, is);
1745 public Map<String, String> getPdbFileNameIdMap()
1747 return pdbFileNameId;
1750 public Map<String, String> getPdbIdFileNameMap()
1752 return pdbIdFileName;