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