JAL-629 Now showing temp fac and secondary structure for File opened structures and...
[jalview.git] / src / jalview / structure / StructureSelectionManager.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.structure;
22
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.IdentityHashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Vector;
34
35 import jalview.analysis.AlignSeq;
36 import jalview.api.StructureSelectionManagerProvider;
37 import jalview.bin.Console;
38 import jalview.commands.CommandI;
39 import jalview.commands.EditCommand;
40 import jalview.commands.OrderCommand;
41 import jalview.datamodel.AlignedCodonFrame;
42 import jalview.datamodel.AlignmentAnnotation;
43 import jalview.datamodel.AlignmentI;
44 import jalview.datamodel.Annotation;
45 import jalview.datamodel.ContiguousI;
46 import jalview.datamodel.HiddenColumns;
47 import jalview.datamodel.PDBEntry;
48 import jalview.datamodel.SearchResults;
49 import jalview.datamodel.SearchResultsI;
50 import jalview.datamodel.SequenceI;
51 import jalview.ext.jmol.JmolParser;
52 import jalview.gui.IProgressIndicator;
53 import jalview.io.AppletFormatAdapter;
54 import jalview.io.DataSourceType;
55 import jalview.io.StructureFile;
56 import jalview.structure.StructureImportSettings.TFType;
57 import jalview.util.MappingUtils;
58 import jalview.util.MessageManager;
59 import jalview.util.Platform;
60 import jalview.ws.sifts.SiftsClient;
61 import jalview.ws.sifts.SiftsException;
62 import jalview.ws.sifts.SiftsSettings;
63 import mc_view.Atom;
64 import mc_view.PDBChain;
65 import mc_view.PDBfile;
66
67 public class StructureSelectionManager
68 {
69   public final static String NEWLINE = System.lineSeparator();
70
71   static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
72
73   private List<StructureMapping> mappings = new ArrayList<>();
74
75   private boolean processSecondaryStructure = false;
76
77   private boolean secStructServices = false;
78
79   private boolean addTempFacAnnot = false;
80
81   /*
82    * Set of any registered mappings between (dataset) sequences.
83    */
84   private List<AlignedCodonFrame> seqmappings = new ArrayList<>();
85
86   private List<CommandListener> commandListeners = new ArrayList<>();
87
88   private List<SelectionListener> sel_listeners = new ArrayList<>();
89
90   /**
91    * @return true if will try to use external services for processing secondary
92    *         structure
93    */
94   public boolean isSecStructServices()
95   {
96     return secStructServices;
97   }
98
99   /**
100    * control use of external services for processing secondary structure
101    * 
102    * @param secStructServices
103    */
104   public void setSecStructServices(boolean secStructServices)
105   {
106     this.secStructServices = secStructServices;
107   }
108
109   /**
110    * flag controlling addition of any kind of structural annotation
111    * 
112    * @return true if temperature factor annotation will be added
113    */
114   public boolean isAddTempFacAnnot()
115   {
116     return addTempFacAnnot;
117   }
118
119   /**
120    * set flag controlling addition of structural annotation
121    * 
122    * @param addTempFacAnnot
123    */
124   public void setAddTempFacAnnot(boolean addTempFacAnnot)
125   {
126     this.addTempFacAnnot = addTempFacAnnot;
127   }
128
129   /**
130    * 
131    * @return if true, the structure manager will attempt to add secondary
132    *         structure lines for unannotated sequences
133    */
134
135   public boolean isProcessSecondaryStructure()
136   {
137     return processSecondaryStructure;
138   }
139
140   /**
141    * Control whether structure manager will try to annotate mapped sequences
142    * with secondary structure from PDB data.
143    * 
144    * @param enable
145    */
146   public void setProcessSecondaryStructure(boolean enable)
147   {
148     processSecondaryStructure = enable;
149   }
150
151   /**
152    * debug function - write all mappings to stdout
153    */
154   public void reportMapping()
155   {
156     if (mappings.isEmpty())
157     {
158       System.err.println("reportMapping: No PDB/Sequence mappings.");
159     }
160     else
161     {
162       System.err.println(
163               "reportMapping: There are " + mappings.size() + " mappings.");
164       int i = 0;
165       for (StructureMapping sm : mappings)
166       {
167         System.err.println("mapping " + i++ + " : " + sm.pdbfile);
168       }
169     }
170   }
171
172   /**
173    * map between the PDB IDs (or structure identifiers) used by Jalview and the
174    * absolute filenames for PDB data that corresponds to it
175    */
176   Map<String, String> pdbIdFileName = new HashMap<>();
177
178   Map<String, String> pdbFileNameId = new HashMap<>();
179
180   public void registerPDBFile(String idForFile, String absoluteFile)
181   {
182     pdbIdFileName.put(idForFile, absoluteFile);
183     pdbFileNameId.put(absoluteFile, idForFile);
184   }
185
186   public String findIdForPDBFile(String idOrFile)
187   {
188     String id = pdbFileNameId.get(idOrFile);
189     return id;
190   }
191
192   public String findFileForPDBId(String idOrFile)
193   {
194     String id = pdbIdFileName.get(idOrFile);
195     return id;
196   }
197
198   public boolean isPDBFileRegistered(String idOrFile)
199   {
200     return pdbFileNameId.containsKey(idOrFile)
201             || pdbIdFileName.containsKey(idOrFile);
202   }
203
204   private static StructureSelectionManager nullProvider = null;
205
206   public static StructureSelectionManager getStructureSelectionManager(
207           StructureSelectionManagerProvider context)
208   {
209     if (context == null)
210     {
211       if (nullProvider == null)
212       {
213         if (instances != null)
214         {
215           throw new Error(MessageManager.getString(
216                   "error.implementation_error_structure_selection_manager_null"),
217                   new NullPointerException(MessageManager
218                           .getString("exception.ssm_context_is_null")));
219         }
220         else
221         {
222           nullProvider = new StructureSelectionManager();
223         }
224         return nullProvider;
225       }
226     }
227     if (instances == null)
228     {
229       instances = new java.util.IdentityHashMap<>();
230     }
231     StructureSelectionManager instance = instances.get(context);
232     if (instance == null)
233     {
234       if (nullProvider != null)
235       {
236         instance = nullProvider;
237       }
238       else
239       {
240         instance = new StructureSelectionManager();
241       }
242       instances.put(context, instance);
243     }
244     return instance;
245   }
246
247   /**
248    * flag controlling whether SeqMappings are relayed from received sequence
249    * mouse over events to other sequences
250    */
251   boolean relaySeqMappings = true;
252
253   /**
254    * Enable or disable relay of seqMapping events to other sequences. You might
255    * want to do this if there are many sequence mappings and the host computer
256    * is slow
257    * 
258    * @param relay
259    */
260   public void setRelaySeqMappings(boolean relay)
261   {
262     relaySeqMappings = relay;
263   }
264
265   /**
266    * get the state of the relay seqMappings flag.
267    * 
268    * @return true if sequence mouse overs are being relayed to other mapped
269    *         sequences
270    */
271   public boolean isRelaySeqMappingsEnabled()
272   {
273     return relaySeqMappings;
274   }
275
276   Vector listeners = new Vector();
277
278   /**
279    * register a listener for alignment sequence mouseover events
280    * 
281    * @param svl
282    */
283   public void addStructureViewerListener(Object svl)
284   {
285     if (!listeners.contains(svl))
286     {
287       listeners.addElement(svl);
288     }
289   }
290
291   /**
292    * Returns the filename the PDB id is already mapped to if known, or null if
293    * it is not mapped
294    * 
295    * @param pdbid
296    * @return
297    */
298   public String alreadyMappedToFile(String pdbid)
299   {
300     for (StructureMapping sm : mappings)
301     {
302       if (sm.getPdbId().equalsIgnoreCase(pdbid))
303       {
304         return sm.pdbfile;
305       }
306     }
307     return null;
308   }
309
310   /**
311    * Import structure data and register a structure mapping for broadcasting
312    * colouring, mouseovers and selection events (convenience wrapper).
313    * 
314    * @param sequence
315    *          - one or more sequences to be mapped to pdbFile
316    * @param targetChains
317    *          - optional chain specification for mapping each sequence to pdb
318    *          (may be nill, individual elements may be nill)
319    * @param pdbFile
320    *          - structure data resource
321    * @param protocol
322    *          - how to resolve data from resource
323    * @return null or the structure data parsed as a pdb file
324    */
325   synchronized public StructureFile setMapping(SequenceI[] sequence,
326           String[] targetChains, String pdbFile, DataSourceType protocol,
327           IProgressIndicator progress)
328   {
329     return computeMapping(true, sequence, targetChains, pdbFile, protocol,
330             progress, null, null, true);
331   }
332
333   /**
334    * Import a single structure file and register sequence structure mappings for
335    * broadcasting colouring, mouseovers and selection events (convenience
336    * wrapper).
337    * 
338    * @param forStructureView
339    *          when true, record the mapping for use in mouseOvers
340    * @param sequence
341    *          - one or more sequences to be mapped to pdbFile
342    * @param targetChains
343    *          - optional chain specification for mapping each sequence to pdb
344    *          (may be nill, individual elements may be nill)
345    * @param pdbFile
346    *          - structure data resource
347    * @param protocol
348    *          - how to resolve data from resource
349    * @return null or the structure data parsed as a pdb file
350    */
351   synchronized public StructureFile setMapping(boolean forStructureView,
352           SequenceI[] sequenceArray, String[] targetChainIds,
353           String pdbFile, DataSourceType sourceType, TFType tft,
354           String paeFilename)
355   {
356     return setMapping(forStructureView, sequenceArray, targetChainIds,
357             pdbFile, sourceType, tft, paeFilename, true);
358   }
359
360   synchronized public StructureFile setMapping(boolean forStructureView,
361           SequenceI[] sequenceArray, String[] targetChainIds,
362           String pdbFile, DataSourceType sourceType, TFType tft,
363           String paeFilename, boolean doXferSettings)
364   {
365     return computeMapping(forStructureView, sequenceArray, targetChainIds,
366             pdbFile, sourceType, null, tft, paeFilename, doXferSettings);
367   }
368
369   /**
370    * create sequence structure mappings between each sequence and the given
371    * pdbFile (retrieved via the given protocol). Either constructs a mapping
372    * using NW alignment or derives one from any available SIFTS mapping data.
373    * 
374    * @param forStructureView
375    *          when true, record the mapping for use in mouseOvers
376    * 
377    * @param sequenceArray
378    *          - one or more sequences to be mapped to pdbFile
379    * @param targetChainIds
380    *          - optional chain specification for mapping each sequence to pdb
381    *          (may be nill, individual elements may be nill) - JBPNote: JAL-2693
382    *          - this should be List<List<String>>, empty lists indicate no
383    *          predefined mappings
384    * @param pdbFile
385    *          - structure data resource
386    * @param sourceType
387    *          - how to resolve data from resource
388    * @param IProgressIndicator
389    *          reference to UI component that maintains a progress bar for the
390    *          mapping operation
391    * @return null or the structure data parsed as a pdb file
392    */
393   synchronized public StructureFile computeMapping(boolean forStructureView,
394           SequenceI[] sequenceArray, String[] targetChainIds,
395           String pdbFile, DataSourceType sourceType,
396           IProgressIndicator progress, TFType tft, String paeFilename,
397           boolean doXferSettings)
398   {
399     long progressSessionId = System.currentTimeMillis() * 3;
400
401     /**
402      * do we extract and transfer annotation from 3D data ?
403      */
404     // FIXME: possibly should just delete
405
406     boolean parseSecStr = processSecondaryStructure
407             && !isStructureFileProcessed(pdbFile, sequenceArray);
408
409     StructureFile pdb = null;
410     boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
411     try
412     {
413       // FIXME if sourceType is not null, we've lost data here
414       sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
415       pdb = new JmolParser(false, pdbFile, sourceType);
416       if (paeFilename != null)
417       {
418         pdb.setPAEMatrix(paeFilename);
419       }
420       pdb.setTemperatureFactorType(tft);
421       pdb.addSettings(parseSecStr && processSecondaryStructure,
422               parseSecStr && addTempFacAnnot,
423               parseSecStr && secStructServices);
424       // save doXferSettings and reset after doParse()
425       boolean temp = pdb.getDoXferSettings();
426       pdb.setDoXferSettings(doXferSettings);
427       pdb.doParse();
428       pdb.setDoXferSettings(temp);
429       if (pdb.getId() != null && pdb.getId().trim().length() > 0
430               && DataSourceType.FILE == sourceType)
431       {
432         registerPDBFile(pdb.getId().trim(), pdbFile);
433       }
434       // if PDBId is unavailable then skip SIFTS mapping execution path
435       // TODO: JAL-3868 need to know if structure is actually from
436       // PDB (has valid PDB ID and has provenance suggesting it
437       // actually came from PDB)
438       boolean isProtein = false;
439       for (SequenceI s : sequenceArray)
440       {
441         if (s.isProtein())
442         {
443           isProtein = true;
444           break;
445         }
446       }
447       isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable()
448               && !pdb.getId().startsWith("AF-") && isProtein;
449
450     } catch (Exception ex)
451     {
452       ex.printStackTrace();
453       return null;
454     }
455     /*
456      * sifts client - non null if SIFTS mappings are to be used 
457      */
458     SiftsClient siftsClient = null;
459     try
460     {
461       if (isMapUsingSIFTs)
462       {
463         siftsClient = new SiftsClient(pdb);
464       }
465     } catch (SiftsException e)
466     {
467       isMapUsingSIFTs = false;
468       Console.error("SIFTS mapping failed", e);
469       Console.error("Falling back on Needleman & Wunsch alignment");
470       siftsClient = null;
471     }
472
473     String targetChainId;
474     for (int s = 0; s < sequenceArray.length; s++)
475     {
476       boolean infChain = true;
477       final SequenceI seq = sequenceArray[s];
478       SequenceI ds = seq;
479       while (ds.getDatasetSequence() != null)
480       {
481         ds = ds.getDatasetSequence();
482       }
483
484       if (targetChainIds != null && targetChainIds[s] != null)
485       {
486         infChain = false;
487         targetChainId = targetChainIds[s];
488       }
489       else if (seq.getName().indexOf("|") > -1)
490       {
491         targetChainId = seq.getName()
492                 .substring(seq.getName().lastIndexOf("|") + 1);
493         if (targetChainId.length() > 1)
494         {
495           if (targetChainId.trim().length() == 0)
496           {
497             targetChainId = " ";
498           }
499           else
500           {
501             // not a valid chain identifier
502             targetChainId = "";
503           }
504         }
505       }
506       else
507       {
508         targetChainId = "";
509       }
510
511       /*
512        * Attempt pairwise alignment of the sequence with each chain in the PDB,
513        * and remember the highest scoring chain
514        */
515       float max = -10;
516       AlignSeq maxAlignseq = null;
517       String maxChainId = " ";
518       PDBChain maxChain = null;
519       boolean first = true;
520       for (PDBChain chain : pdb.getChains())
521       {
522         if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
523                 && !infChain)
524         {
525           continue; // don't try to map chains don't match.
526         }
527         // TODO: correctly determine sequence type for mixed na/peptide
528         // structures
529         final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
530         AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
531                 type);
532         // equivalent to:
533         // AlignSeq as = new AlignSeq(sequence[s], chain.sequence, type);
534         // as.calcScoreMatrix();
535         // as.traceAlignment();
536
537         if (first || as.maxscore > max
538                 || (as.maxscore == max && chain.id.equals(targetChainId)))
539         {
540           first = false;
541           maxChain = chain;
542           max = as.maxscore;
543           maxAlignseq = as;
544           maxChainId = chain.id;
545         }
546       }
547       if (maxChain == null)
548       {
549         continue;
550       }
551
552       if (sourceType == DataSourceType.PASTE)
553       {
554         pdbFile = "INLINE" + pdb.getId();
555       }
556
557       List<StructureMapping> seqToStrucMapping = new ArrayList<>();
558       if (isMapUsingSIFTs && seq.isProtein())
559       {
560         if (progress != null)
561         {
562           progress.setProgressBar(
563                   MessageManager
564                           .getString("status.obtaining_mapping_with_sifts"),
565                   progressSessionId);
566         }
567         jalview.datamodel.Mapping sqmpping = maxAlignseq
568                 .getMappingFromS1(false);
569         if (targetChainId != null && !targetChainId.trim().isEmpty())
570         {
571           StructureMapping siftsMapping;
572           try
573           {
574             siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
575                     pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
576             seqToStrucMapping.add(siftsMapping);
577             maxChain.makeExactMapping(siftsMapping, seq);
578             maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS",
579                     pdb.getId().toLowerCase(Locale.ROOT));
580             maxChain.transferResidueAnnotation(siftsMapping, null);
581             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
582
583           } catch (SiftsException e)
584           {
585             // fall back to NW alignment
586             Console.error(e.getMessage());
587             StructureMapping nwMapping = getNWMappings(seq, pdbFile,
588                     targetChainId, maxChain, pdb, maxAlignseq);
589             seqToStrucMapping.add(nwMapping);
590             maxChain.makeExactMapping(maxAlignseq, seq);
591             maxChain.transferRESNUMFeatures(seq, "IEA:Jalview",
592                     pdb.getId().toLowerCase(Locale.ROOT)); // FIXME: is
593             // this
594             // "IEA:Jalview" ?
595             maxChain.transferResidueAnnotation(nwMapping, sqmpping);
596             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
597           }
598         }
599         else
600         {
601           List<StructureMapping> foundSiftsMappings = new ArrayList<>();
602           for (PDBChain chain : pdb.getChains())
603           {
604             StructureMapping siftsMapping = null;
605             try
606             {
607               siftsMapping = getStructureMapping(seq, pdbFile, chain.id,
608                       pdb, chain, sqmpping, maxAlignseq, siftsClient);
609               foundSiftsMappings.add(siftsMapping);
610               chain.makeExactMapping(siftsMapping, seq);
611               chain.transferRESNUMFeatures(seq, "IEA: SIFTS",
612                       pdb.getId().toLowerCase(Locale.ROOT));// FIXME: is this
613               // "IEA:SIFTS" ?
614               chain.transferResidueAnnotation(siftsMapping, null);
615             } catch (SiftsException e)
616             {
617               System.err.println(e.getMessage());
618             } catch (Exception e)
619             {
620               System.err.println(
621                       "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair");
622               System.err.println(e.getMessage());
623             }
624           }
625           if (!foundSiftsMappings.isEmpty())
626           {
627             seqToStrucMapping.addAll(foundSiftsMappings);
628             ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
629           }
630           else
631           {
632             StructureMapping nwMapping = getNWMappings(seq, pdbFile,
633                     maxChainId, maxChain, pdb, maxAlignseq);
634             seqToStrucMapping.add(nwMapping);
635             maxChain.transferRESNUMFeatures(seq, null,
636                     pdb.getId().toLowerCase(Locale.ROOT)); // FIXME: is this
637             // "IEA:Jalview" ?
638             maxChain.transferResidueAnnotation(nwMapping, sqmpping);
639             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
640           }
641         }
642       }
643       else
644       {
645         if (progress != null)
646         {
647           progress.setProgressBar(
648                   MessageManager.getString(
649                           "status.obtaining_mapping_with_nw_alignment"),
650                   progressSessionId);
651         }
652         StructureMapping nwMapping = getNWMappings(seq, pdbFile, maxChainId,
653                 maxChain, pdb, maxAlignseq);
654         seqToStrucMapping.add(nwMapping);
655         ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
656       }
657       if (forStructureView)
658       {
659         for (StructureMapping sm : seqToStrucMapping)
660         {
661           addStructureMapping(sm); // not addAll!
662         }
663       }
664       if (progress != null)
665       {
666         progress.setProgressBar(null, progressSessionId);
667       }
668     }
669     return pdb;
670   }
671
672   /**
673    * check if we need to extract secondary structure from given pdbFile and
674    * transfer to sequences
675    * 
676    * @param pdbFile
677    * @param sequenceArray
678    * @return
679    */
680   private boolean isStructureFileProcessed(String pdbFile,
681           SequenceI[] sequenceArray)
682   {
683     boolean processed = false;
684     if (isPDBFileRegistered(pdbFile))
685     {
686       for (SequenceI sq : sequenceArray)
687       {
688         SequenceI ds = sq;
689         while (ds.getDatasetSequence() != null)
690         {
691           ds = ds.getDatasetSequence();
692         }
693         ;
694         if (ds.getAnnotation() != null)
695         {
696           for (AlignmentAnnotation ala : ds.getAnnotation())
697           {
698             // false if any annotation present from this structure
699             // JBPNote this fails for jmol/chimera view because the *file* is
700             // passed, not the structure data ID -
701             if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
702             {
703               processed = true;
704             }
705           }
706         }
707       }
708     }
709     return processed;
710   }
711
712   public void addStructureMapping(StructureMapping sm)
713   {
714     if (!mappings.contains(sm))
715     {
716       mappings.add(sm);
717     }
718   }
719
720   /**
721    * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
722    * uniprot or PDB
723    * 
724    * @param seq
725    * @param pdbFile
726    * @param targetChainId
727    * @param pdb
728    * @param maxChain
729    * @param sqmpping
730    * @param maxAlignseq
731    * @param siftsClient
732    *          client for retrieval of SIFTS mappings for this structure
733    * @return
734    * @throws SiftsException
735    */
736   private StructureMapping getStructureMapping(SequenceI seq,
737           String pdbFile, String targetChainId, StructureFile pdb,
738           PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
739           AlignSeq maxAlignseq, SiftsClient siftsClient)
740           throws SiftsException
741   {
742     StructureMapping curChainMapping = siftsClient
743             .getSiftsStructureMapping(seq, pdbFile, targetChainId);
744     try
745     {
746       PDBChain chain = pdb.findChain(targetChainId);
747       if (chain != null)
748       {
749         chain.transferResidueAnnotation(curChainMapping, null);
750       }
751     } catch (Exception e)
752     {
753       e.printStackTrace();
754     }
755     return curChainMapping;
756   }
757
758   private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
759           String maxChainId, PDBChain maxChain, StructureFile pdb,
760           AlignSeq maxAlignseq)
761   {
762     final StringBuilder mappingDetails = new StringBuilder(128);
763     mappingDetails.append(NEWLINE)
764             .append("Sequence \u27f7 Structure mapping details");
765     mappingDetails.append(NEWLINE);
766     mappingDetails
767             .append("Method: inferred with Needleman & Wunsch alignment");
768     mappingDetails.append(NEWLINE).append("PDB Sequence is :")
769             .append(NEWLINE).append("Sequence = ")
770             .append(maxChain.sequence.getSequenceAsString());
771     mappingDetails.append(NEWLINE).append("No of residues = ")
772             .append(maxChain.residues.size()).append(NEWLINE)
773             .append(NEWLINE);
774     PrintStream ps = new PrintStream(System.out)
775     {
776       @Override
777       public void print(String x)
778       {
779         mappingDetails.append(x);
780       }
781
782       @Override
783       public void println()
784       {
785         mappingDetails.append(NEWLINE);
786       }
787     };
788
789     maxAlignseq.printAlignment(ps);
790
791     mappingDetails.append(NEWLINE).append("PDB start/end ");
792     mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
793             .append(" ");
794     mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
795     mappingDetails.append(NEWLINE).append("SEQ start/end ");
796     mappingDetails
797             .append(String
798                     .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
799             .append(" ");
800     mappingDetails.append(
801             String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
802     mappingDetails.append(NEWLINE);
803     maxChain.makeExactMapping(maxAlignseq, seq);
804     jalview.datamodel.Mapping sqmpping = maxAlignseq
805             .getMappingFromS1(false);
806     maxChain.transferRESNUMFeatures(seq, null,
807             pdb.getId().toLowerCase(Locale.ROOT));
808
809     HashMap<Integer, int[]> mapping = new HashMap<>();
810     int resNum = -10000;
811     int index = 0;
812     char insCode = ' ';
813
814     do
815     {
816       Atom tmp = maxChain.atoms.elementAt(index);
817       if ((resNum != tmp.resNumber || insCode != tmp.insCode)
818               && tmp.alignmentMapping != -1)
819       {
820         resNum = tmp.resNumber;
821         insCode = tmp.insCode;
822         if (tmp.alignmentMapping >= -1)
823         {
824           mapping.put(tmp.alignmentMapping + 1,
825                   new int[]
826                   { tmp.resNumber, tmp.atomIndex });
827         }
828       }
829
830       index++;
831     } while (index < maxChain.atoms.size());
832
833     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
834             pdb.getId(), maxChainId, mapping, mappingDetails.toString());
835     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
836     return nwMapping;
837   }
838
839   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
840   {
841     listeners.removeElement(svl);
842     if (svl instanceof SequenceListener)
843     {
844       for (int i = 0; i < listeners.size(); i++)
845       {
846         if (listeners.elementAt(i) instanceof StructureListener)
847         {
848           ((StructureListener) listeners.elementAt(i))
849                   .releaseReferences(svl);
850         }
851       }
852     }
853
854     if (pdbfiles == null)
855     {
856       return;
857     }
858
859     /*
860      * Remove mappings to the closed listener's PDB files, but first check if
861      * another listener is still interested
862      */
863     List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
864
865     StructureListener sl;
866     for (int i = 0; i < listeners.size(); i++)
867     {
868       if (listeners.elementAt(i) instanceof StructureListener)
869       {
870         sl = (StructureListener) listeners.elementAt(i);
871         for (String pdbfile : sl.getStructureFiles())
872         {
873           pdbs.remove(pdbfile);
874         }
875       }
876     }
877
878     /*
879      * Rebuild the mappings set, retaining only those which are for 'other' PDB
880      * files
881      */
882     if (pdbs.size() > 0)
883     {
884       List<StructureMapping> tmp = new ArrayList<>();
885       for (StructureMapping sm : mappings)
886       {
887         if (!pdbs.contains(sm.pdbfile))
888         {
889           tmp.add(sm);
890         }
891       }
892
893       mappings = tmp;
894     }
895   }
896
897   /**
898    * hack to highlight a range of positions at once on any structure views
899    * 
900    * @param sequenceRef
901    * @param is
902    *          - series of int start-end ranges as positions on sequenceRef
903    * @param i
904    * @param object
905    */
906   public void highlightPositionsOn(SequenceI sequenceRef, int[][] is,
907           Object source)
908   {
909     boolean hasSequenceListeners = handlingVamsasMo
910             || !seqmappings.isEmpty();
911     SearchResultsI results = null;
912     ArrayList<Integer> listOfPositions = new ArrayList<Integer>();
913     for (int[] s_e : is)
914     {
915       for (int p = s_e[0]; p <= s_e[1]; listOfPositions.add(p++))
916         ;
917     }
918     int seqpos[] = new int[listOfPositions.size()];
919     int i = 0;
920     for (Integer p : listOfPositions)
921     {
922       seqpos[i++] = p;
923     }
924
925     for (i = 0; i < listeners.size(); i++)
926     {
927       Object listener = listeners.elementAt(i);
928       if (listener == source)
929       {
930         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
931         // Temporary fudge with SequenceListener.getVamsasSource()
932         continue;
933       }
934       if (listener instanceof StructureListener)
935       {
936         highlightStructure((StructureListener) listener, sequenceRef,
937                 seqpos);
938       }
939
940     }
941   }
942
943   /**
944    * Propagate mouseover of a single position in a structure
945    * 
946    * @param pdbResNum
947    * @param chain
948    * @param pdbfile
949    * @return
950    */
951   public String mouseOverStructure(int pdbResNum, String chain,
952           String pdbfile)
953   {
954     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
955     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
956     return mouseOverStructure(atoms);
957   }
958
959   /**
960    * Propagate mouseover or selection of multiple positions in a structure
961    * 
962    * @param atoms
963    */
964   public String mouseOverStructure(List<AtomSpec> atoms)
965   {
966     if (listeners == null)
967     {
968       // old or prematurely sent event
969       return null;
970     }
971     boolean hasSequenceListener = false;
972     for (int i = 0; i < listeners.size(); i++)
973     {
974       if (listeners.elementAt(i) instanceof SequenceListener)
975       {
976         hasSequenceListener = true;
977       }
978     }
979     if (!hasSequenceListener)
980     {
981       return null;
982     }
983
984     SearchResultsI results = findAlignmentPositionsForStructurePositions(
985             atoms);
986     String result = null;
987     for (Object li : listeners)
988     {
989       if (li instanceof SequenceListener)
990       {
991         String s = ((SequenceListener) li).highlightSequence(results);
992         if (s != null)
993         {
994           result = s;
995         }
996       }
997     }
998     return result;
999   }
1000
1001   /**
1002    * Constructs a SearchResults object holding regions (if any) in the Jalview
1003    * alignment which have a mapping to the structure viewer positions in the
1004    * supplied list
1005    * 
1006    * @param atoms
1007    * @return
1008    */
1009   public SearchResultsI findAlignmentPositionsForStructurePositions(
1010           List<AtomSpec> atoms)
1011   {
1012     SearchResultsI results = new SearchResults();
1013     for (AtomSpec atom : atoms)
1014     {
1015       SequenceI lastseq = null;
1016       int lastipos = -1;
1017       for (StructureMapping sm : mappings)
1018       {
1019         if (sm.pdbfile.equals(atom.getPdbFile())
1020                 && sm.pdbchain.equals(atom.getChain()))
1021         {
1022           int indexpos = sm.getSeqPos(atom.getPdbResNum());
1023           if (lastipos != indexpos || lastseq != sm.sequence)
1024           {
1025             results.addResult(sm.sequence, indexpos, indexpos);
1026             lastipos = indexpos;
1027             lastseq = sm.sequence;
1028             // construct highlighted sequence list
1029             for (AlignedCodonFrame acf : seqmappings)
1030             {
1031               acf.markMappedRegion(sm.sequence, indexpos, results);
1032             }
1033           }
1034         }
1035       }
1036     }
1037     return results;
1038   }
1039
1040   /**
1041    * highlight regions associated with a position (indexpos) in seq
1042    * 
1043    * @param seq
1044    *          the sequence that the mouse over occurred on
1045    * @param indexpos
1046    *          the absolute position being mouseovered in seq (0 to seq.length())
1047    * @param seqPos
1048    *          the sequence position (if -1, seq.findPosition is called to
1049    *          resolve the residue number)
1050    */
1051   public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
1052           VamsasSource source)
1053   {
1054     boolean hasSequenceListeners = handlingVamsasMo
1055             || !seqmappings.isEmpty();
1056     SearchResultsI results = null;
1057     if (seqPos == -1)
1058     {
1059       seqPos = seq.findPosition(indexpos);
1060     }
1061     for (int i = 0; i < listeners.size(); i++)
1062     {
1063       Object listener = listeners.elementAt(i);
1064       if (listener == source)
1065       {
1066         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1067         // Temporary fudge with SequenceListener.getVamsasSource()
1068         continue;
1069       }
1070       if (listener instanceof StructureListener)
1071       {
1072         highlightStructure((StructureListener) listener, seq, seqPos);
1073       }
1074       else
1075       {
1076         if (listener instanceof SequenceListener)
1077         {
1078           final SequenceListener seqListener = (SequenceListener) listener;
1079           if (hasSequenceListeners
1080                   && seqListener.getVamsasSource() != source)
1081           {
1082             if (relaySeqMappings)
1083             {
1084               if (results == null)
1085               {
1086                 results = MappingUtils.buildSearchResults(seq, seqPos,
1087                         seqmappings);
1088               }
1089               if (handlingVamsasMo)
1090               {
1091                 results.addResult(seq, seqPos, seqPos);
1092
1093               }
1094               if (!results.isEmpty())
1095               {
1096                 seqListener.highlightSequence(results);
1097               }
1098             }
1099           }
1100         }
1101         else if (listener instanceof VamsasListener && !handlingVamsasMo)
1102         {
1103           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
1104                   source);
1105         }
1106         else if (listener instanceof SecondaryStructureListener)
1107         {
1108           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
1109                   indexpos, seqPos);
1110         }
1111       }
1112     }
1113   }
1114
1115   /**
1116    * Send suitable messages to a StructureListener to highlight atoms
1117    * corresponding to the given sequence position(s)
1118    * 
1119    * @param sl
1120    * @param seq
1121    * @param positions
1122    */
1123   public void highlightStructure(StructureListener sl, SequenceI seq,
1124           int... positions)
1125   {
1126     if (!sl.isListeningFor(seq))
1127     {
1128       return;
1129     }
1130     int atomNo;
1131     List<AtomSpec> atoms = new ArrayList<>();
1132     for (StructureMapping sm : mappings)
1133     {
1134       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1135               || (sm.sequence.getDatasetSequence() != null && sm.sequence
1136                       .getDatasetSequence() == seq.getDatasetSequence()))
1137       {
1138         for (int index : positions)
1139         {
1140           atomNo = sm.getAtomNum(index);
1141
1142           if (atomNo > 0)
1143           {
1144             atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1145                     sm.getPDBResNum(index), atomNo));
1146           }
1147         }
1148       }
1149     }
1150     sl.highlightAtoms(atoms);
1151   }
1152
1153   public void highlightStructureRegionsFor(StructureListener sl,
1154           SequenceI[] seqs, int... columns)
1155   {
1156     List<SequenceI> to_highlight = new ArrayList<SequenceI>();
1157     for (SequenceI seq : seqs)
1158     {
1159       if (sl.isListeningFor(seq))
1160       {
1161         to_highlight.add(seq);
1162       }
1163     }
1164     if (to_highlight.size() == 0)
1165     {
1166       return;
1167     }
1168     List<AtomSpec> atoms = new ArrayList<>();
1169     for (SequenceI seq : to_highlight)
1170     {
1171       int atomNo;
1172       for (StructureMapping sm : mappings)
1173       {
1174         if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1175                 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1176                         .getDatasetSequence() == seq.getDatasetSequence()))
1177         {
1178
1179           for (int i = 0; i < columns.length; i += 2)
1180           {
1181             ContiguousI positions = seq.findPositions(columns[i] + 1,
1182                     columns[i + 1] + 1);
1183             if (positions == null)
1184             {
1185               continue;
1186             }
1187             for (int index = positions.getBegin(); index <= positions
1188                     .getEnd(); index++)
1189             {
1190
1191               atomNo = sm.getAtomNum(index);
1192
1193               if (atomNo > 0)
1194               {
1195                 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1196                         sm.getPDBResNum(index), atomNo));
1197               }
1198             }
1199           }
1200         }
1201       }
1202       if (atoms.size() > 0)
1203       {
1204         sl.highlightAtoms(atoms);
1205       }
1206     }
1207   }
1208
1209   /**
1210    * true if a mouse over event from an external (ie Vamsas) source is being
1211    * handled
1212    */
1213   boolean handlingVamsasMo = false;
1214
1215   long lastmsg = 0;
1216
1217   /**
1218    * as mouseOverSequence but only route event to SequenceListeners
1219    * 
1220    * @param sequenceI
1221    * @param position
1222    *          in an alignment sequence
1223    */
1224   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
1225           VamsasSource source)
1226   {
1227     handlingVamsasMo = true;
1228     long msg = sequenceI.hashCode() * (1 + position);
1229     if (lastmsg != msg)
1230     {
1231       lastmsg = msg;
1232       mouseOverSequence(sequenceI, position, -1, source);
1233     }
1234     handlingVamsasMo = false;
1235   }
1236
1237   public Annotation[] colourSequenceFromStructure(SequenceI seq,
1238           String pdbid)
1239   {
1240     return null;
1241     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
1242     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
1243     /*
1244      * Annotation [] annotations = new Annotation[seq.getLength()];
1245      * 
1246      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
1247      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
1248      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
1249      * 
1250      * for (int j = 0; j < mappings.length; j++) {
1251      * 
1252      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
1253      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
1254      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
1255      * "+mappings[j].pdbfile);
1256      * 
1257      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
1258      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
1259      * 
1260      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
1261      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
1262      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
1263      * mappings[j].pdbfile); }
1264      * 
1265      * annotations[index] = new Annotation("X",null,' ',0,col); } return
1266      * annotations; } } } }
1267      * 
1268      * return annotations;
1269      */
1270   }
1271
1272   public void structureSelectionChanged()
1273   {
1274   }
1275
1276   public void sequenceSelectionChanged()
1277   {
1278   }
1279
1280   public void sequenceColoursChanged(Object source)
1281   {
1282     StructureListener sl;
1283     for (int i = 0; i < listeners.size(); i++)
1284     {
1285       if (listeners.elementAt(i) instanceof StructureListener)
1286       {
1287         sl = (StructureListener) listeners.elementAt(i);
1288         sl.updateColours(source);
1289       }
1290     }
1291   }
1292
1293   public StructureMapping[] getMapping(String pdbfile)
1294   {
1295     List<StructureMapping> tmp = new ArrayList<>();
1296     for (StructureMapping sm : mappings)
1297     {
1298       if (sm.pdbfile.equals(pdbfile))
1299       {
1300         tmp.add(sm);
1301       }
1302     }
1303     return tmp.toArray(new StructureMapping[tmp.size()]);
1304   }
1305
1306   /**
1307    * Returns a readable description of all mappings for the given pdbfile to any
1308    * of the given sequences
1309    * 
1310    * @param pdbfile
1311    * @param seqs
1312    * @return
1313    */
1314   public String printMappings(String pdbfile, List<SequenceI> seqs)
1315   {
1316     if (pdbfile == null || seqs == null || seqs.isEmpty())
1317     {
1318       return "";
1319     }
1320
1321     StringBuilder sb = new StringBuilder(64);
1322     for (StructureMapping sm : mappings)
1323     {
1324       if (Platform.pathEquals(sm.pdbfile, pdbfile)
1325               && seqs.contains(sm.sequence))
1326       {
1327         sb.append(sm.mappingDetails);
1328         sb.append(NEWLINE);
1329         // separator makes it easier to read multiple mappings
1330         sb.append("=====================");
1331         sb.append(NEWLINE);
1332       }
1333     }
1334     sb.append(NEWLINE);
1335
1336     return sb.toString();
1337   }
1338
1339   /**
1340    * Remove the given mapping
1341    * 
1342    * @param acf
1343    */
1344   public void deregisterMapping(AlignedCodonFrame acf)
1345   {
1346     if (acf != null)
1347     {
1348       boolean removed = seqmappings.remove(acf);
1349       if (removed && seqmappings.isEmpty())
1350       { // debug
1351         System.out.println("All mappings removed");
1352       }
1353     }
1354   }
1355
1356   /**
1357    * Add each of the given codonFrames to the stored set, if not aready present.
1358    * 
1359    * @param mappings
1360    */
1361   public void registerMappings(List<AlignedCodonFrame> mappings)
1362   {
1363     if (mappings != null)
1364     {
1365       for (AlignedCodonFrame acf : mappings)
1366       {
1367         registerMapping(acf);
1368       }
1369     }
1370   }
1371
1372   /**
1373    * Add the given mapping to the stored set, unless already stored.
1374    */
1375   public void registerMapping(AlignedCodonFrame acf)
1376   {
1377     if (acf != null)
1378     {
1379       if (!seqmappings.contains(acf))
1380       {
1381         seqmappings.add(acf);
1382       }
1383     }
1384   }
1385
1386   /**
1387    * Resets this object to its initial state by removing all registered
1388    * listeners, codon mappings, PDB file mappings
1389    */
1390   public void resetAll()
1391   {
1392     if (mappings != null)
1393     {
1394       mappings.clear();
1395     }
1396     if (seqmappings != null)
1397     {
1398       seqmappings.clear();
1399     }
1400     if (sel_listeners != null)
1401     {
1402       sel_listeners.clear();
1403     }
1404     if (listeners != null)
1405     {
1406       listeners.clear();
1407     }
1408     if (commandListeners != null)
1409     {
1410       commandListeners.clear();
1411     }
1412     if (view_listeners != null)
1413     {
1414       view_listeners.clear();
1415     }
1416     if (pdbFileNameId != null)
1417     {
1418       pdbFileNameId.clear();
1419     }
1420     if (pdbIdFileName != null)
1421     {
1422       pdbIdFileName.clear();
1423     }
1424   }
1425
1426   public void addSelectionListener(SelectionListener selecter)
1427   {
1428     if (!sel_listeners.contains(selecter))
1429     {
1430       sel_listeners.add(selecter);
1431     }
1432   }
1433
1434   public void removeSelectionListener(SelectionListener toremove)
1435   {
1436     if (sel_listeners.contains(toremove))
1437     {
1438       sel_listeners.remove(toremove);
1439     }
1440   }
1441
1442   public synchronized void sendSelection(
1443           jalview.datamodel.SequenceGroup selection,
1444           jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
1445           SelectionSource source)
1446   {
1447     for (SelectionListener slis : sel_listeners)
1448     {
1449       if (slis != source)
1450       {
1451         slis.selection(selection, colsel, hidden, source);
1452       }
1453     }
1454   }
1455
1456   Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
1457
1458   public synchronized void sendViewPosition(
1459           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1460           int startSeq, int endSeq)
1461   {
1462
1463     if (view_listeners != null && view_listeners.size() > 0)
1464     {
1465       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1466               .elements();
1467       while (listeners.hasMoreElements())
1468       {
1469         AlignmentViewPanelListener slis = listeners.nextElement();
1470         if (slis != source)
1471         {
1472           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1473         }
1474         ;
1475       }
1476     }
1477   }
1478
1479   /**
1480    * release all references associated with this manager provider
1481    * 
1482    * @param jalviewLite
1483    */
1484   public static void release(StructureSelectionManagerProvider jalviewLite)
1485   {
1486     // synchronized (instances)
1487     {
1488       if (instances == null)
1489       {
1490         return;
1491       }
1492       StructureSelectionManager mnger = (instances.get(jalviewLite));
1493       if (mnger != null)
1494       {
1495         instances.remove(jalviewLite);
1496         try
1497         {
1498           /* bsoares 2019-03-20 finalize deprecated, no apparent external
1499            * resources to close
1500            */
1501           // mnger.finalize();
1502         } catch (Throwable x)
1503         {
1504         }
1505       }
1506     }
1507   }
1508
1509   public void registerPDBEntry(PDBEntry pdbentry)
1510   {
1511     if (pdbentry.getFile() != null
1512             && pdbentry.getFile().trim().length() > 0)
1513     {
1514       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1515     }
1516   }
1517
1518   public void addCommandListener(CommandListener cl)
1519   {
1520     if (!commandListeners.contains(cl))
1521     {
1522       commandListeners.add(cl);
1523     }
1524   }
1525
1526   public boolean hasCommandListener(CommandListener cl)
1527   {
1528     return this.commandListeners.contains(cl);
1529   }
1530
1531   public boolean removeCommandListener(CommandListener l)
1532   {
1533     return commandListeners.remove(l);
1534   }
1535
1536   /**
1537    * Forward a command to any command listeners (except for the command's
1538    * source).
1539    * 
1540    * @param command
1541    *          the command to be broadcast (in its form after being performed)
1542    * @param undo
1543    *          if true, the command was being 'undone'
1544    * @param source
1545    */
1546   public void commandPerformed(CommandI command, boolean undo,
1547           VamsasSource source)
1548   {
1549     for (CommandListener listener : commandListeners)
1550     {
1551       listener.mirrorCommand(command, undo, this, source);
1552     }
1553   }
1554
1555   /**
1556    * Returns a new CommandI representing the given command as mapped to the
1557    * given sequences. If no mapping could be made, or the command is not of a
1558    * mappable kind, returns null.
1559    * 
1560    * @param command
1561    * @param undo
1562    * @param mapTo
1563    * @param gapChar
1564    * @return
1565    */
1566   public CommandI mapCommand(CommandI command, boolean undo,
1567           final AlignmentI mapTo, char gapChar)
1568   {
1569     if (command instanceof EditCommand)
1570     {
1571       return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
1572               gapChar, seqmappings);
1573     }
1574     else if (command instanceof OrderCommand)
1575     {
1576       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1577               mapTo, seqmappings);
1578     }
1579     return null;
1580   }
1581
1582   public List<AlignedCodonFrame> getSequenceMappings()
1583   {
1584     return seqmappings;
1585   }
1586
1587   /**
1588    * quick and dirty route to just highlight all structure positions for a range
1589    * of columns
1590    * 
1591    * @param sequencesArray
1592    * @param is
1593    *          start-end columns on sequencesArray
1594    * @param source
1595    *          origin parent AlignmentPanel
1596    */
1597   public void highlightPositionsOnMany(SequenceI[] sequencesArray, int[] is,
1598           Object source)
1599   {
1600     for (int i = 0; i < listeners.size(); i++)
1601     {
1602       Object listener = listeners.elementAt(i);
1603       if (listener == source)
1604       {
1605         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1606         // Temporary fudge with SequenceListener.getVamsasSource()
1607         continue;
1608       }
1609       if (listener instanceof StructureListener)
1610       {
1611         highlightStructureRegionsFor((StructureListener) listener,
1612                 sequencesArray, is);
1613       }
1614     }
1615   }
1616
1617   public Map<String, String> getPdbFileNameIdMap()
1618   {
1619     return pdbFileNameId;
1620   }
1621
1622   public Map<String, String> getPdbIdFileNameMap()
1623   {
1624     return pdbIdFileName;
1625   }
1626
1627 }