8968742741733cba46dff74e43f931fb582e7ddd
[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       jalview.bin.Console.errPrintln("reportMapping: No PDB/Sequence mappings.");
159     }
160     else
161     {
162       jalview.bin.Console.errPrintln(
163               "reportMapping: There are " + mappings.size() + " mappings.");
164       int i = 0;
165       for (StructureMapping sm : mappings)
166       {
167         jalview.bin.Console.errPrintln("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    * @param tft - specify how to interpret the temperature factor column in the atom data
392    * @param paeFilename - when not null, specifies a filename containing a matrix formatted in JSON using one of the known PAE formats
393    * @param doXferSettings - when true, transfer annotation to mapped sequences in sequenceArray 
394    * @return null or the structure data parsed as a pdb file
395    */
396   synchronized public StructureFile computeMapping(boolean forStructureView,
397           SequenceI[] sequenceArray, String[] targetChainIds,
398           String pdbFile, DataSourceType sourceType,
399           IProgressIndicator progress, TFType tft, String paeFilename,
400           boolean doXferSettings)
401   {
402     long progressSessionId = System.currentTimeMillis() * 3;
403
404     /**
405      * do we extract and transfer annotation from 3D data ?
406      */
407     // FIXME: possibly should just delete
408
409     boolean parseSecStr = processSecondaryStructure
410             && !isStructureFileProcessed(pdbFile, sequenceArray);
411
412     StructureFile pdb = null;
413     boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
414     try
415     {
416       // FIXME if sourceType is not null, we've lost data here
417       sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
418       pdb = new JmolParser(false, pdbFile, sourceType);
419       if (paeFilename != null)
420       {
421         pdb.setPAEMatrix(paeFilename);
422       }
423       pdb.setTemperatureFactorType(tft);
424       pdb.addSettings(parseSecStr && processSecondaryStructure,
425               parseSecStr && addTempFacAnnot,
426               parseSecStr && secStructServices);
427       // save doXferSettings and reset after doParse()
428       boolean temp = pdb.getDoXferSettings();
429       pdb.setDoXferSettings(doXferSettings);
430       pdb.doParse();
431       pdb.setDoXferSettings(temp);
432       if (pdb.getId() != null && pdb.getId().trim().length() > 0
433               && DataSourceType.FILE == sourceType)
434       {
435         registerPDBFile(pdb.getId().trim(), pdbFile);
436       }
437       // if PDBId is unavailable then skip SIFTS mapping execution path
438       // TODO: JAL-3868 need to know if structure is actually from
439       // PDB (has valid PDB ID and has provenance suggesting it
440       // actually came from PDB)
441       boolean isProtein = false;
442       for (SequenceI s : sequenceArray)
443       {
444         if (s.isProtein())
445         {
446           isProtein = true;
447           break;
448         }
449       }
450       isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable()
451               && !pdb.getId().startsWith("AF-") && isProtein;
452
453     } catch (Exception ex)
454     {
455       ex.printStackTrace();
456       return null;
457     }
458     /*
459      * sifts client - non null if SIFTS mappings are to be used 
460      */
461     SiftsClient siftsClient = null;
462     try
463     {
464       if (isMapUsingSIFTs)
465       {
466         siftsClient = new SiftsClient(pdb);
467       }
468     } catch (SiftsException e)
469     {
470       isMapUsingSIFTs = false;
471       Console.error("SIFTS mapping failed", e);
472       Console.error("Falling back on Needleman & Wunsch alignment");
473       siftsClient = null;
474     }
475
476     String targetChainId;
477     for (int s = 0; s < sequenceArray.length; s++)
478     {
479       boolean infChain = true;
480       final SequenceI seq = sequenceArray[s];
481       SequenceI ds = seq;
482       while (ds.getDatasetSequence() != null)
483       {
484         ds = ds.getDatasetSequence();
485       }
486
487       if (targetChainIds != null && targetChainIds[s] != null)
488       {
489         infChain = false;
490         targetChainId = targetChainIds[s];
491       }
492       else if (seq.getName().indexOf("|") > -1)
493       {
494         targetChainId = seq.getName()
495                 .substring(seq.getName().lastIndexOf("|") + 1);
496         if (targetChainId.length() > 1)
497         {
498           if (targetChainId.trim().length() == 0)
499           {
500             targetChainId = " ";
501           }
502           else
503           {
504             // not a valid chain identifier
505             targetChainId = "";
506           }
507         }
508       }
509       else
510       {
511         targetChainId = "";
512       }
513
514       /*
515        * Attempt pairwise alignment of the sequence with each chain in the PDB,
516        * and remember the highest scoring chain
517        */
518       float max = -10;
519       AlignSeq maxAlignseq = null;
520       String maxChainId = " ";
521       PDBChain maxChain = null;
522       boolean first = true;
523       for (PDBChain chain : pdb.getChains())
524       {
525         if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
526                 && !infChain)
527         {
528           continue; // don't try to map chains don't match.
529         }
530         // TODO: correctly determine sequence type for mixed na/peptide
531         // structures
532         final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
533         AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
534                 type);
535         // equivalent to:
536         // AlignSeq as = new AlignSeq(sequence[s], chain.sequence, type);
537         // as.calcScoreMatrix();
538         // as.traceAlignment();
539
540         if (first || as.maxscore > max
541                 || (as.maxscore == max && chain.id.equals(targetChainId)))
542         {
543           first = false;
544           maxChain = chain;
545           max = as.maxscore;
546           maxAlignseq = as;
547           maxChainId = chain.id;
548         }
549       }
550       if (maxChain == null)
551       {
552         continue;
553       }
554
555       if (sourceType == DataSourceType.PASTE)
556       {
557         pdbFile = "INLINE" + pdb.getId();
558       }
559
560       List<StructureMapping> seqToStrucMapping = new ArrayList<>();
561       if (isMapUsingSIFTs && seq.isProtein())
562       {
563         if (progress != null)
564         {
565           progress.setProgressBar(
566                   MessageManager
567                           .getString("status.obtaining_mapping_with_sifts"),
568                   progressSessionId);
569         }
570         jalview.datamodel.Mapping sqmpping = maxAlignseq
571                 .getMappingFromS1(false);
572         if (targetChainId != null && !targetChainId.trim().isEmpty())
573         {
574           StructureMapping siftsMapping;
575           try
576           {
577             siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
578                     pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
579             seqToStrucMapping.add(siftsMapping);
580             maxChain.makeExactMapping(siftsMapping, seq);
581             maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS",
582                     pdb.getId().toLowerCase(Locale.ROOT));
583             maxChain.transferResidueAnnotation(siftsMapping, null);
584             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
585
586           } catch (SiftsException e)
587           {
588             // fall back to NW alignment
589             Console.error(e.getMessage());
590             StructureMapping nwMapping = getNWMappings(seq, pdbFile,
591                     targetChainId, maxChain, pdb, maxAlignseq);
592             seqToStrucMapping.add(nwMapping);
593             maxChain.makeExactMapping(maxAlignseq, seq);
594             maxChain.transferRESNUMFeatures(seq, "IEA:Jalview",
595                     pdb.getId().toLowerCase(Locale.ROOT)); // FIXME: is
596             // this
597             // "IEA:Jalview" ?
598             maxChain.transferResidueAnnotation(nwMapping, sqmpping);
599             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
600           }
601         }
602         else
603         {
604           List<StructureMapping> foundSiftsMappings = new ArrayList<>();
605           for (PDBChain chain : pdb.getChains())
606           {
607             StructureMapping siftsMapping = null;
608             try
609             {
610               siftsMapping = getStructureMapping(seq, pdbFile, chain.id,
611                       pdb, chain, sqmpping, maxAlignseq, siftsClient);
612               foundSiftsMappings.add(siftsMapping);
613               chain.makeExactMapping(siftsMapping, seq);
614               chain.transferRESNUMFeatures(seq, "IEA: SIFTS",
615                       pdb.getId().toLowerCase(Locale.ROOT));// FIXME: is this
616               // "IEA:SIFTS" ?
617               chain.transferResidueAnnotation(siftsMapping, null);
618             } catch (SiftsException e)
619             {
620               jalview.bin.Console.errPrintln(e.getMessage());
621             } catch (Exception e)
622             {
623               jalview.bin.Console.errPrintln(
624                       "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair");
625               jalview.bin.Console.errPrintln(e.getMessage());
626             }
627           }
628           if (!foundSiftsMappings.isEmpty())
629           {
630             seqToStrucMapping.addAll(foundSiftsMappings);
631             ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
632           }
633           else
634           {
635             StructureMapping nwMapping = getNWMappings(seq, pdbFile,
636                     maxChainId, maxChain, pdb, maxAlignseq);
637             seqToStrucMapping.add(nwMapping);
638             maxChain.transferRESNUMFeatures(seq, null,
639                     pdb.getId().toLowerCase(Locale.ROOT)); // FIXME: is this
640             // "IEA:Jalview" ?
641             maxChain.transferResidueAnnotation(nwMapping, sqmpping);
642             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
643           }
644         }
645       }
646       else
647       {
648         if (progress != null)
649         {
650           progress.setProgressBar(
651                   MessageManager.getString(
652                           "status.obtaining_mapping_with_nw_alignment"),
653                   progressSessionId);
654         }
655         StructureMapping nwMapping = getNWMappings(seq, pdbFile, maxChainId,
656                 maxChain, pdb, maxAlignseq);
657         seqToStrucMapping.add(nwMapping);
658         ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
659       }
660       if (forStructureView)
661       {
662         for (StructureMapping sm : seqToStrucMapping)
663         {
664           addStructureMapping(sm); // not addAll!
665         }
666       }
667       if (progress != null)
668       {
669         progress.setProgressBar(null, progressSessionId);
670       }
671     }
672     return pdb;
673   }
674
675   /**
676    * check if we need to extract secondary structure from given pdbFile and
677    * transfer to sequences
678    * 
679    * @param pdbFile
680    * @param sequenceArray
681    * @return
682    */
683   private boolean isStructureFileProcessed(String pdbFile,
684           SequenceI[] sequenceArray)
685   {
686     boolean processed = false;
687     if (isPDBFileRegistered(pdbFile))
688     {
689       for (SequenceI sq : sequenceArray)
690       {
691         SequenceI ds = sq;
692         while (ds.getDatasetSequence() != null)
693         {
694           ds = ds.getDatasetSequence();
695         }
696         ;
697         if (ds.getAnnotation() != null)
698         {
699           for (AlignmentAnnotation ala : ds.getAnnotation())
700           {
701             // false if any annotation present from this structure
702             // JBPNote this fails for jmol/chimera view because the *file* is
703             // passed, not the structure data ID -
704             if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
705             {
706               processed = true;
707             }
708           }
709         }
710       }
711     }
712     return processed;
713   }
714
715   public void addStructureMapping(StructureMapping sm)
716   {
717     if (!mappings.contains(sm))
718     {
719       mappings.add(sm);
720     }
721   }
722
723   /**
724    * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
725    * uniprot or PDB
726    * 
727    * @param seq
728    * @param pdbFile
729    * @param targetChainId
730    * @param pdb
731    * @param maxChain
732    * @param sqmpping
733    * @param maxAlignseq
734    * @param siftsClient
735    *          client for retrieval of SIFTS mappings for this structure
736    * @return
737    * @throws SiftsException
738    */
739   private StructureMapping getStructureMapping(SequenceI seq,
740           String pdbFile, String targetChainId, StructureFile pdb,
741           PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
742           AlignSeq maxAlignseq, SiftsClient siftsClient)
743           throws SiftsException
744   {
745     StructureMapping curChainMapping = siftsClient
746             .getSiftsStructureMapping(seq, pdbFile, targetChainId);
747     try
748     {
749       PDBChain chain = pdb.findChain(targetChainId);
750       if (chain != null)
751       {
752         chain.transferResidueAnnotation(curChainMapping, null);
753       }
754     } catch (Exception e)
755     {
756       e.printStackTrace();
757     }
758     return curChainMapping;
759   }
760
761   private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
762           String maxChainId, PDBChain maxChain, StructureFile pdb,
763           AlignSeq maxAlignseq)
764   {
765     final StringBuilder mappingDetails = new StringBuilder(128);
766     mappingDetails.append(NEWLINE)
767             .append("Sequence \u27f7 Structure mapping details");
768     mappingDetails.append(NEWLINE);
769     mappingDetails
770             .append("Method: inferred with Needleman & Wunsch alignment");
771     mappingDetails.append(NEWLINE).append("PDB Sequence is :")
772             .append(NEWLINE).append("Sequence = ")
773             .append(maxChain.sequence.getSequenceAsString());
774     mappingDetails.append(NEWLINE).append("No of residues = ")
775             .append(maxChain.residues.size()).append(NEWLINE)
776             .append(NEWLINE);
777     PrintStream ps = new PrintStream(System.out)
778     {
779       @Override
780       public void print(String x)
781       {
782         mappingDetails.append(x);
783       }
784
785       @Override
786       public void println()
787       {
788         mappingDetails.append(NEWLINE);
789       }
790     };
791
792     maxAlignseq.printAlignment(ps);
793
794     mappingDetails.append(NEWLINE).append("PDB start/end ");
795     mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
796             .append(" ");
797     mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
798     mappingDetails.append(NEWLINE).append("SEQ start/end ");
799     mappingDetails
800             .append(String
801                     .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
802             .append(" ");
803     mappingDetails.append(
804             String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
805     mappingDetails.append(NEWLINE);
806     maxChain.makeExactMapping(maxAlignseq, seq);
807     jalview.datamodel.Mapping sqmpping = maxAlignseq
808             .getMappingFromS1(false);
809     maxChain.transferRESNUMFeatures(seq, null,
810             pdb.getId().toLowerCase(Locale.ROOT));
811
812     HashMap<Integer, int[]> mapping = new HashMap<>();
813     int resNum = -10000;
814     int index = 0;
815     char insCode = ' ';
816
817     do
818     {
819       Atom tmp = maxChain.atoms.elementAt(index);
820       if ((resNum != tmp.resNumber || insCode != tmp.insCode)
821               && tmp.alignmentMapping != -1)
822       {
823         resNum = tmp.resNumber;
824         insCode = tmp.insCode;
825         if (tmp.alignmentMapping >= -1)
826         {
827           mapping.put(tmp.alignmentMapping + 1,
828                   new int[]
829                   { tmp.resNumber, tmp.atomIndex });
830         }
831       }
832
833       index++;
834     } while (index < maxChain.atoms.size());
835
836     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
837             pdb.getId(), maxChainId, mapping, mappingDetails.toString());
838     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
839     return nwMapping;
840   }
841
842   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
843   {
844     listeners.removeElement(svl);
845     if (svl instanceof SequenceListener)
846     {
847       for (int i = 0; i < listeners.size(); i++)
848       {
849         if (listeners.elementAt(i) instanceof StructureListener)
850         {
851           ((StructureListener) listeners.elementAt(i))
852                   .releaseReferences(svl);
853         }
854       }
855     }
856
857     if (pdbfiles == null)
858     {
859       return;
860     }
861
862     /*
863      * Remove mappings to the closed listener's PDB files, but first check if
864      * another listener is still interested
865      */
866     List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
867
868     StructureListener sl;
869     for (int i = 0; i < listeners.size(); i++)
870     {
871       if (listeners.elementAt(i) instanceof StructureListener)
872       {
873         sl = (StructureListener) listeners.elementAt(i);
874         for (String pdbfile : sl.getStructureFiles())
875         {
876           pdbs.remove(pdbfile);
877         }
878       }
879     }
880
881     /*
882      * Rebuild the mappings set, retaining only those which are for 'other' PDB
883      * files
884      */
885     if (pdbs.size() > 0)
886     {
887       List<StructureMapping> tmp = new ArrayList<>();
888       for (StructureMapping sm : mappings)
889       {
890         if (!pdbs.contains(sm.pdbfile))
891         {
892           tmp.add(sm);
893         }
894       }
895
896       mappings = tmp;
897     }
898   }
899
900   /**
901    * hack to highlight a range of positions at once on any structure views
902    * 
903    * @param sequenceRef
904    * @param is
905    *          - series of int start-end ranges as positions on sequenceRef
906    * @param i
907    * @param object
908    */
909   public void highlightPositionsOn(SequenceI sequenceRef, int[][] is,
910           Object source)
911   {
912     boolean hasSequenceListeners = handlingVamsasMo
913             || !seqmappings.isEmpty();
914     SearchResultsI results = null;
915     ArrayList<Integer> listOfPositions = new ArrayList<Integer>();
916     for (int[] s_e : is)
917     {
918       for (int p = s_e[0]; p <= s_e[1]; listOfPositions.add(p++))
919         ;
920     }
921     int seqpos[] = new int[listOfPositions.size()];
922     int i = 0;
923     for (Integer p : listOfPositions)
924     {
925       seqpos[i++] = p;
926     }
927
928     for (i = 0; i < listeners.size(); i++)
929     {
930       Object listener = listeners.elementAt(i);
931       if (listener == source)
932       {
933         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
934         // Temporary fudge with SequenceListener.getVamsasSource()
935         continue;
936       }
937       if (listener instanceof StructureListener)
938       {
939         highlightStructure((StructureListener) listener, sequenceRef,
940                 seqpos);
941       }
942
943     }
944   }
945
946   /**
947    * Propagate mouseover of a single position in a structure
948    * 
949    * @param pdbResNum
950    * @param chain
951    * @param pdbfile
952    * @return
953    */
954   public String mouseOverStructure(int pdbResNum, String chain,
955           String pdbfile)
956   {
957     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
958     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
959     return mouseOverStructure(atoms);
960   }
961
962   /**
963    * Propagate mouseover or selection of multiple positions in a structure
964    * 
965    * @param atoms
966    */
967   public String mouseOverStructure(List<AtomSpec> atoms)
968   {
969     if (listeners == null)
970     {
971       // old or prematurely sent event
972       return null;
973     }
974     boolean hasSequenceListener = false;
975     for (int i = 0; i < listeners.size(); i++)
976     {
977       if (listeners.elementAt(i) instanceof SequenceListener)
978       {
979         hasSequenceListener = true;
980       }
981     }
982     if (!hasSequenceListener)
983     {
984       return null;
985     }
986
987     SearchResultsI results = findAlignmentPositionsForStructurePositions(
988             atoms);
989     String result = null;
990     for (Object li : listeners)
991     {
992       if (li instanceof SequenceListener)
993       {
994         String s = ((SequenceListener) li).highlightSequence(results);
995         if (s != null)
996         {
997           result = s;
998         }
999       }
1000     }
1001     return result;
1002   }
1003
1004   /**
1005    * Constructs a SearchResults object holding regions (if any) in the Jalview
1006    * alignment which have a mapping to the structure viewer positions in the
1007    * supplied list
1008    * 
1009    * @param atoms
1010    * @return
1011    */
1012   public SearchResultsI findAlignmentPositionsForStructurePositions(
1013           List<AtomSpec> atoms)
1014   {
1015     SearchResultsI results = new SearchResults();
1016     for (AtomSpec atom : atoms)
1017     {
1018       SequenceI lastseq = null;
1019       int lastipos = -1;
1020       for (StructureMapping sm : mappings)
1021       {
1022         if (sm.pdbfile.equals(atom.getPdbFile())
1023                 && sm.pdbchain.equals(atom.getChain()))
1024         {
1025           int indexpos = sm.getSeqPos(atom.getPdbResNum());
1026           if (lastipos != indexpos || lastseq != sm.sequence)
1027           {
1028             results.addResult(sm.sequence, indexpos, indexpos);
1029             lastipos = indexpos;
1030             lastseq = sm.sequence;
1031             // construct highlighted sequence list
1032             for (AlignedCodonFrame acf : seqmappings)
1033             {
1034               acf.markMappedRegion(sm.sequence, indexpos, results);
1035             }
1036           }
1037         }
1038       }
1039     }
1040     return results;
1041   }
1042
1043   /**
1044    * highlight regions associated with a position (indexpos) in seq
1045    * 
1046    * @param seq
1047    *          the sequence that the mouse over occurred on
1048    * @param indexpos
1049    *          the absolute position being mouseovered in seq (0 to seq.length())
1050    * @param seqPos
1051    *          the sequence position (if -1, seq.findPosition is called to
1052    *          resolve the residue number)
1053    */
1054   public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
1055           VamsasSource source)
1056   {
1057     boolean hasSequenceListeners = handlingVamsasMo
1058             || !seqmappings.isEmpty();
1059     SearchResultsI results = null;
1060     if (seqPos == -1)
1061     {
1062       seqPos = seq.findPosition(indexpos);
1063     }
1064     for (int i = 0; i < listeners.size(); i++)
1065     {
1066       Object listener = listeners.elementAt(i);
1067       if (listener == source)
1068       {
1069         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1070         // Temporary fudge with SequenceListener.getVamsasSource()
1071         continue;
1072       }
1073       if (listener instanceof StructureListener)
1074       {
1075         highlightStructure((StructureListener) listener, seq, seqPos);
1076       }
1077       else
1078       {
1079         if (listener instanceof SequenceListener)
1080         {
1081           final SequenceListener seqListener = (SequenceListener) listener;
1082           if (hasSequenceListeners
1083                   && seqListener.getVamsasSource() != source)
1084           {
1085             if (relaySeqMappings)
1086             {
1087               if (results == null)
1088               {
1089                 results = MappingUtils.buildSearchResults(seq, seqPos,
1090                         seqmappings);
1091               }
1092               if (handlingVamsasMo)
1093               {
1094                 results.addResult(seq, seqPos, seqPos);
1095
1096               }
1097               if (!results.isEmpty())
1098               {
1099                 seqListener.highlightSequence(results);
1100               }
1101             }
1102           }
1103         }
1104         else if (listener instanceof VamsasListener && !handlingVamsasMo)
1105         {
1106           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
1107                   source);
1108         }
1109         else if (listener instanceof SecondaryStructureListener)
1110         {
1111           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
1112                   indexpos, seqPos);
1113         }
1114       }
1115     }
1116   }
1117
1118   /**
1119    * Send suitable messages to a StructureListener to highlight atoms
1120    * corresponding to the given sequence position(s)
1121    * 
1122    * @param sl
1123    * @param seq
1124    * @param positions
1125    */
1126   public void highlightStructure(StructureListener sl, SequenceI seq,
1127           int... positions)
1128   {
1129     if (!sl.isListeningFor(seq))
1130     {
1131       return;
1132     }
1133     int atomNo;
1134     List<AtomSpec> atoms = new ArrayList<>();
1135     for (StructureMapping sm : mappings)
1136     {
1137       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1138               || (sm.sequence.getDatasetSequence() != null && sm.sequence
1139                       .getDatasetSequence() == seq.getDatasetSequence()))
1140       {
1141         for (int index : positions)
1142         {
1143           atomNo = sm.getAtomNum(index);
1144
1145           if (atomNo > 0)
1146           {
1147             atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1148                     sm.getPDBResNum(index), atomNo));
1149           }
1150         }
1151       }
1152     }
1153     sl.highlightAtoms(atoms);
1154   }
1155
1156   public void highlightStructureRegionsFor(StructureListener sl,
1157           SequenceI[] seqs, int... columns)
1158   {
1159     List<SequenceI> to_highlight = new ArrayList<SequenceI>();
1160     for (SequenceI seq : seqs)
1161     {
1162       if (sl.isListeningFor(seq))
1163       {
1164         to_highlight.add(seq);
1165       }
1166     }
1167     if (to_highlight.size() == 0)
1168     {
1169       return;
1170     }
1171     List<AtomSpec> atoms = new ArrayList<>();
1172     for (SequenceI seq : to_highlight)
1173     {
1174       int atomNo;
1175       for (StructureMapping sm : mappings)
1176       {
1177         if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1178                 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1179                         .getDatasetSequence() == seq.getDatasetSequence()))
1180         {
1181
1182           for (int i = 0; i < columns.length; i += 2)
1183           {
1184             ContiguousI positions = seq.findPositions(columns[i] + 1,
1185                     columns[i + 1] + 1);
1186             if (positions == null)
1187             {
1188               continue;
1189             }
1190             for (int index = positions.getBegin(); index <= positions
1191                     .getEnd(); index++)
1192             {
1193
1194               atomNo = sm.getAtomNum(index);
1195
1196               if (atomNo > 0)
1197               {
1198                 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1199                         sm.getPDBResNum(index), atomNo));
1200               }
1201             }
1202           }
1203         }
1204       }
1205       if (atoms.size() > 0)
1206       {
1207         sl.highlightAtoms(atoms);
1208       }
1209     }
1210   }
1211
1212   /**
1213    * true if a mouse over event from an external (ie Vamsas) source is being
1214    * handled
1215    */
1216   boolean handlingVamsasMo = false;
1217
1218   long lastmsg = 0;
1219
1220   /**
1221    * as mouseOverSequence but only route event to SequenceListeners
1222    * 
1223    * @param sequenceI
1224    * @param position
1225    *          in an alignment sequence
1226    */
1227   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
1228           VamsasSource source)
1229   {
1230     handlingVamsasMo = true;
1231     long msg = sequenceI.hashCode() * (1 + position);
1232     if (lastmsg != msg)
1233     {
1234       lastmsg = msg;
1235       mouseOverSequence(sequenceI, position, -1, source);
1236     }
1237     handlingVamsasMo = false;
1238   }
1239
1240   public Annotation[] colourSequenceFromStructure(SequenceI seq,
1241           String pdbid)
1242   {
1243     return null;
1244     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
1245     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
1246     /*
1247      * Annotation [] annotations = new Annotation[seq.getLength()];
1248      * 
1249      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
1250      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
1251      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
1252      * 
1253      * for (int j = 0; j < mappings.length; j++) {
1254      * 
1255      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
1256      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
1257      * jalview.bin.Console.outPrintln(pdbid+" "+mappings[j].getPdbId() +"
1258      * "+mappings[j].pdbfile);
1259      * 
1260      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
1261      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
1262      * 
1263      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
1264      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
1265      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
1266      * mappings[j].pdbfile); }
1267      * 
1268      * annotations[index] = new Annotation("X",null,' ',0,col); } return
1269      * annotations; } } } }
1270      * 
1271      * return annotations;
1272      */
1273   }
1274
1275   public void structureSelectionChanged()
1276   {
1277   }
1278
1279   public void sequenceSelectionChanged()
1280   {
1281   }
1282
1283   public void sequenceColoursChanged(Object source)
1284   {
1285     StructureListener sl;
1286     for (int i = 0; i < listeners.size(); i++)
1287     {
1288       if (listeners.elementAt(i) instanceof StructureListener)
1289       {
1290         sl = (StructureListener) listeners.elementAt(i);
1291         sl.updateColours(source);
1292       }
1293     }
1294   }
1295
1296   public StructureMapping[] getMapping(String pdbfile)
1297   {
1298     List<StructureMapping> tmp = new ArrayList<>();
1299     for (StructureMapping sm : mappings)
1300     {
1301       if (sm.pdbfile.equals(pdbfile))
1302       {
1303         tmp.add(sm);
1304       }
1305     }
1306     return tmp.toArray(new StructureMapping[tmp.size()]);
1307   }
1308
1309   /**
1310    * Returns a readable description of all mappings for the given pdbfile to any
1311    * of the given sequences
1312    * 
1313    * @param pdbfile
1314    * @param seqs
1315    * @return
1316    */
1317   public String printMappings(String pdbfile, List<SequenceI> seqs)
1318   {
1319     if (pdbfile == null || seqs == null || seqs.isEmpty())
1320     {
1321       return "";
1322     }
1323
1324     StringBuilder sb = new StringBuilder(64);
1325     for (StructureMapping sm : mappings)
1326     {
1327       if (Platform.pathEquals(sm.pdbfile, pdbfile)
1328               && seqs.contains(sm.sequence))
1329       {
1330         sb.append(sm.mappingDetails);
1331         sb.append(NEWLINE);
1332         // separator makes it easier to read multiple mappings
1333         sb.append("=====================");
1334         sb.append(NEWLINE);
1335       }
1336     }
1337     sb.append(NEWLINE);
1338
1339     return sb.toString();
1340   }
1341
1342   /**
1343    * Remove the given mapping
1344    * 
1345    * @param acf
1346    */
1347   public void deregisterMapping(AlignedCodonFrame acf)
1348   {
1349     if (acf != null)
1350     {
1351       boolean removed = seqmappings.remove(acf);
1352       if (removed && seqmappings.isEmpty())
1353       { // debug
1354         jalview.bin.Console.outPrintln("All mappings removed");
1355       }
1356     }
1357   }
1358
1359   /**
1360    * Add each of the given codonFrames to the stored set, if not aready present.
1361    * 
1362    * @param mappings
1363    */
1364   public void registerMappings(List<AlignedCodonFrame> mappings)
1365   {
1366     if (mappings != null)
1367     {
1368       for (AlignedCodonFrame acf : mappings)
1369       {
1370         registerMapping(acf);
1371       }
1372     }
1373   }
1374
1375   /**
1376    * Add the given mapping to the stored set, unless already stored.
1377    */
1378   public void registerMapping(AlignedCodonFrame acf)
1379   {
1380     if (acf != null)
1381     {
1382       if (!seqmappings.contains(acf))
1383       {
1384         seqmappings.add(acf);
1385       }
1386     }
1387   }
1388
1389   /**
1390    * Resets this object to its initial state by removing all registered
1391    * listeners, codon mappings, PDB file mappings
1392    */
1393   public void resetAll()
1394   {
1395     if (mappings != null)
1396     {
1397       mappings.clear();
1398     }
1399     if (seqmappings != null)
1400     {
1401       seqmappings.clear();
1402     }
1403     if (sel_listeners != null)
1404     {
1405       sel_listeners.clear();
1406     }
1407     if (listeners != null)
1408     {
1409       listeners.clear();
1410     }
1411     if (commandListeners != null)
1412     {
1413       commandListeners.clear();
1414     }
1415     if (view_listeners != null)
1416     {
1417       view_listeners.clear();
1418     }
1419     if (pdbFileNameId != null)
1420     {
1421       pdbFileNameId.clear();
1422     }
1423     if (pdbIdFileName != null)
1424     {
1425       pdbIdFileName.clear();
1426     }
1427   }
1428
1429   public void addSelectionListener(SelectionListener selecter)
1430   {
1431     if (!sel_listeners.contains(selecter))
1432     {
1433       sel_listeners.add(selecter);
1434     }
1435   }
1436
1437   public void removeSelectionListener(SelectionListener toremove)
1438   {
1439     if (sel_listeners.contains(toremove))
1440     {
1441       sel_listeners.remove(toremove);
1442     }
1443   }
1444
1445   public synchronized void sendSelection(
1446           jalview.datamodel.SequenceGroup selection,
1447           jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
1448           SelectionSource source)
1449   {
1450     for (SelectionListener slis : sel_listeners)
1451     {
1452       if (slis != source)
1453       {
1454         slis.selection(selection, colsel, hidden, source);
1455       }
1456     }
1457   }
1458
1459   Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
1460
1461   public synchronized void sendViewPosition(
1462           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1463           int startSeq, int endSeq)
1464   {
1465
1466     if (view_listeners != null && view_listeners.size() > 0)
1467     {
1468       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1469               .elements();
1470       while (listeners.hasMoreElements())
1471       {
1472         AlignmentViewPanelListener slis = listeners.nextElement();
1473         if (slis != source)
1474         {
1475           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1476         }
1477         ;
1478       }
1479     }
1480   }
1481
1482   /**
1483    * release all references associated with this manager provider
1484    * 
1485    * @param jalviewLite
1486    */
1487   public static void release(StructureSelectionManagerProvider jalviewLite)
1488   {
1489     // synchronized (instances)
1490     {
1491       if (instances == null)
1492       {
1493         return;
1494       }
1495       StructureSelectionManager mnger = (instances.get(jalviewLite));
1496       if (mnger != null)
1497       {
1498         instances.remove(jalviewLite);
1499         try
1500         {
1501           /* bsoares 2019-03-20 finalize deprecated, no apparent external
1502            * resources to close
1503            */
1504           // mnger.finalize();
1505         } catch (Throwable x)
1506         {
1507         }
1508       }
1509     }
1510   }
1511
1512   public void registerPDBEntry(PDBEntry pdbentry)
1513   {
1514     if (pdbentry.getFile() != null
1515             && pdbentry.getFile().trim().length() > 0)
1516     {
1517       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1518     }
1519   }
1520
1521   public void addCommandListener(CommandListener cl)
1522   {
1523     if (!commandListeners.contains(cl))
1524     {
1525       commandListeners.add(cl);
1526     }
1527   }
1528
1529   public boolean hasCommandListener(CommandListener cl)
1530   {
1531     return this.commandListeners.contains(cl);
1532   }
1533
1534   public boolean removeCommandListener(CommandListener l)
1535   {
1536     return commandListeners.remove(l);
1537   }
1538
1539   /**
1540    * Forward a command to any command listeners (except for the command's
1541    * source).
1542    * 
1543    * @param command
1544    *          the command to be broadcast (in its form after being performed)
1545    * @param undo
1546    *          if true, the command was being 'undone'
1547    * @param source
1548    */
1549   public void commandPerformed(CommandI command, boolean undo,
1550           VamsasSource source)
1551   {
1552     for (CommandListener listener : commandListeners)
1553     {
1554       listener.mirrorCommand(command, undo, this, source);
1555     }
1556   }
1557
1558   /**
1559    * Returns a new CommandI representing the given command as mapped to the
1560    * given sequences. If no mapping could be made, or the command is not of a
1561    * mappable kind, returns null.
1562    * 
1563    * @param command
1564    * @param undo
1565    * @param mapTo
1566    * @param gapChar
1567    * @return
1568    */
1569   public CommandI mapCommand(CommandI command, boolean undo,
1570           final AlignmentI mapTo, char gapChar)
1571   {
1572     if (command instanceof EditCommand)
1573     {
1574       return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
1575               gapChar, seqmappings);
1576     }
1577     else if (command instanceof OrderCommand)
1578     {
1579       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1580               mapTo, seqmappings);
1581     }
1582     return null;
1583   }
1584
1585   public List<AlignedCodonFrame> getSequenceMappings()
1586   {
1587     return seqmappings;
1588   }
1589
1590   /**
1591    * quick and dirty route to just highlight all structure positions for a range
1592    * of columns
1593    * 
1594    * @param sequencesArray
1595    * @param is
1596    *          start-end columns on sequencesArray
1597    * @param source
1598    *          origin parent AlignmentPanel
1599    */
1600   public void highlightPositionsOnMany(SequenceI[] sequencesArray, int[] is,
1601           Object source)
1602   {
1603     for (int i = 0; i < listeners.size(); i++)
1604     {
1605       Object listener = listeners.elementAt(i);
1606       if (listener == source)
1607       {
1608         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1609         // Temporary fudge with SequenceListener.getVamsasSource()
1610         continue;
1611       }
1612       if (listener instanceof StructureListener)
1613       {
1614         highlightStructureRegionsFor((StructureListener) listener,
1615                 sequencesArray, is);
1616       }
1617     }
1618   }
1619
1620   public Map<String, String> getPdbFileNameIdMap()
1621   {
1622     return pdbFileNameId;
1623   }
1624
1625   public Map<String, String> getPdbIdFileNameMap()
1626   {
1627     return pdbIdFileName;
1628   }
1629
1630 }