JAL-2422 general pull-up/removal of common or unused fields and methods
[jalview.git] / src / jalview / structures / models / AAStructureBindingModel.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.structures.models;
22
23 import jalview.api.AlignmentViewPanel;
24 import jalview.api.FeatureRenderer;
25 import jalview.api.SequenceRenderer;
26 import jalview.api.StructureSelectionManagerProvider;
27 import jalview.api.structures.JalviewStructureDisplayI;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.HiddenColumns;
30 import jalview.datamodel.PDBEntry;
31 import jalview.datamodel.SequenceI;
32 import jalview.io.DataSourceType;
33 import jalview.schemes.ColourSchemeI;
34 import jalview.schemes.ResidueProperties;
35 import jalview.structure.AtomSpec;
36 import jalview.structure.StructureCommandsI;
37 import jalview.structure.StructureListener;
38 import jalview.structure.StructureMapping;
39 import jalview.structure.StructureSelectionManager;
40 import jalview.util.Comparison;
41 import jalview.util.MessageManager;
42
43 import java.awt.Color;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.BitSet;
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.Map;
50
51 import javax.swing.SwingUtilities;
52
53 /**
54  * 
55  * A base class to hold common function for protein structure model binding.
56  * Initial version created by refactoring JMol and Chimera binding models, but
57  * other structure viewers could in principle be accommodated in future.
58  * 
59  * @author gmcarstairs
60  *
61  */
62 public abstract class AAStructureBindingModel
63         extends SequenceStructureBindingModel
64         implements StructureListener, StructureSelectionManagerProvider
65 {
66   private static final String COLOURING_STRUCTURES = MessageManager
67           .getString("status.colouring_structures");
68
69   /*
70    * the Jalview panel through which the user interacts
71    * with the structure viewer
72    */
73   private JalviewStructureDisplayI viewer;
74
75   /*
76    * helper that generates command syntax
77    */
78   private StructureCommandsI commandGenerator;
79
80   private StructureSelectionManager ssm;
81
82   /*
83    * modelled chains, formatted as "pdbid:chainCode"
84    */
85   private List<String> chainNames;
86
87   /*
88    * lookup of pdb file name by key "pdbid:chainCode"
89    */
90   private Map<String, String> chainFile;
91
92   /*
93    * distinct PDB entries (pdb files) associated
94    * with sequences
95    */
96   private PDBEntry[] pdbEntry;
97
98   /*
99    * sequences mapped to each pdbentry
100    */
101   private SequenceI[][] sequence;
102
103   /*
104    * array of target chains for sequences - tied to pdbentry and sequence[]
105    */
106   private String[][] chains;
107
108   /*
109    * datasource protocol for access to PDBEntrylatest
110    */
111   DataSourceType protocol = null;
112
113   protected boolean colourBySequence = true;
114
115   private boolean nucleotide;
116
117   private boolean finishedInit = false;
118
119   /**
120    * current set of model filenames loaded in the Jmol instance
121    */
122   protected String[] modelFileNames = null;
123
124   public String fileLoadingError;
125
126   /**
127    * Data bean class to simplify parameterisation in superposeStructures
128    */
129   protected class SuperposeData
130   {
131     /**
132      * Constructor with alignment width argument
133      * 
134      * @param width
135      */
136     public SuperposeData(int width)
137     {
138       pdbResNo = new int[width];
139     }
140
141     public String filename;
142
143     public String pdbId;
144
145     public String chain = "";
146
147     public boolean isRna;
148
149     /*
150      * The pdb residue number (if any) mapped to each column of the alignment
151      */
152     public int[] pdbResNo;
153   }
154
155   /**
156    * Constructor
157    * 
158    * @param ssm
159    * @param seqs
160    */
161   public AAStructureBindingModel(StructureSelectionManager ssm,
162           SequenceI[][] seqs)
163   {
164     this.ssm = ssm;
165     this.sequence = seqs;
166     chainNames = new ArrayList<>();
167     chainFile = new HashMap<>();
168   }
169
170   /**
171    * Constructor
172    * 
173    * @param ssm
174    * @param pdbentry
175    * @param sequenceIs
176    * @param protocol
177    */
178   public AAStructureBindingModel(StructureSelectionManager ssm,
179           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
180           DataSourceType protocol)
181   {
182     this(ssm, sequenceIs);
183     this.nucleotide = Comparison.isNucleotide(sequenceIs);
184     this.pdbEntry = pdbentry;
185     this.protocol = protocol;
186     resolveChains();
187   }
188
189   private boolean resolveChains()
190   {
191     /**
192      * final count of chain mappings discovered
193      */
194     int chainmaps = 0;
195     // JBPNote: JAL-2693 - this should be a list of chain mappings per
196     // [pdbentry][sequence]
197     String[][] newchains = new String[pdbEntry.length][];
198     int pe = 0;
199     for (PDBEntry pdb : pdbEntry)
200     {
201       SequenceI[] seqsForPdb = sequence[pe];
202       if (seqsForPdb != null)
203       {
204         newchains[pe] = new String[seqsForPdb.length];
205         int se = 0;
206         for (SequenceI asq : seqsForPdb)
207         {
208           String chain = (chains != null && chains[pe] != null)
209                   ? chains[pe][se]
210                   : null;
211           SequenceI sq = (asq.getDatasetSequence() == null) ? asq
212                   : asq.getDatasetSequence();
213           if (sq.getAllPDBEntries() != null)
214           {
215             for (PDBEntry pdbentry : sq.getAllPDBEntries())
216             {
217               if (pdb.getFile() != null && pdbentry.getFile() != null
218                       && pdb.getFile().equals(pdbentry.getFile()))
219               {
220                 String chaincode = pdbentry.getChainCode();
221                 if (chaincode != null && chaincode.length() > 0)
222                 {
223                   chain = chaincode;
224                   chainmaps++;
225                   break;
226                 }
227               }
228             }
229           }
230           newchains[pe][se] = chain;
231           se++;
232         }
233         pe++;
234       }
235     }
236
237     chains = newchains;
238     return chainmaps > 0;
239   }
240   public StructureSelectionManager getSsm()
241   {
242     return ssm;
243   }
244
245   /**
246    * Returns the i'th PDBEntry (or null)
247    * 
248    * @param i
249    * @return
250    */
251   public PDBEntry getPdbEntry(int i)
252   {
253     return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
254   }
255
256   /**
257    * Answers true if this binding includes the given PDB id, else false
258    * 
259    * @param pdbId
260    * @return
261    */
262   public boolean hasPdbId(String pdbId)
263   {
264     if (pdbEntry != null)
265     {
266       for (PDBEntry pdb : pdbEntry)
267       {
268         if (pdb.getId().equals(pdbId))
269         {
270           return true;
271         }
272       }
273     }
274     return false;
275   }
276
277   /**
278    * Returns the number of modelled PDB file entries.
279    * 
280    * @return
281    */
282   public int getPdbCount()
283   {
284     return pdbEntry == null ? 0 : pdbEntry.length;
285   }
286
287   public SequenceI[][] getSequence()
288   {
289     return sequence;
290   }
291
292   public String[][] getChains()
293   {
294     return chains;
295   }
296
297   public DataSourceType getProtocol()
298   {
299     return protocol;
300   }
301
302   // TODO may remove this if calling methods can be pulled up here
303   protected void setPdbentry(PDBEntry[] pdbentry)
304   {
305     this.pdbEntry = pdbentry;
306   }
307
308   protected void setSequence(SequenceI[][] sequence)
309   {
310     this.sequence = sequence;
311   }
312
313   protected void setChains(String[][] chains)
314   {
315     this.chains = chains;
316   }
317
318   /**
319    * Construct a title string for the viewer window based on the data Jalview
320    * knows about
321    * 
322    * @param viewerName
323    *          TODO
324    * @param verbose
325    * 
326    * @return
327    */
328   public String getViewerTitle(String viewerName, boolean verbose)
329   {
330     if (getSequence() == null || getSequence().length < 1
331             || getPdbCount() < 1 || getSequence()[0].length < 1)
332     {
333       return ("Jalview " + viewerName + " Window");
334     }
335     // TODO: give a more informative title when multiple structures are
336     // displayed.
337     StringBuilder title = new StringBuilder(64);
338     final PDBEntry pdbe = getPdbEntry(0);
339     title.append(viewerName + " view for " + getSequence()[0][0].getName()
340             + ":" + pdbe.getId());
341
342     if (verbose)
343     {
344       String method = (String) pdbe.getProperty("method");
345       if (method != null)
346       {
347         title.append(" Method: ").append(method);
348       }
349       String chain = (String) pdbe.getProperty("chains");
350       if (chain != null)
351       {
352         title.append(" Chain:").append(chain);
353       }
354     }
355     return title.toString();
356   }
357
358   /**
359    * Called by after closeViewer is called, to release any resources and
360    * references so they can be garbage collected. Override if needed.
361    */
362   protected void releaseUIResources()
363   {
364   }
365
366   @Override
367   public void releaseReferences(Object svl)
368   {
369   }
370
371   public boolean isColourBySequence()
372   {
373     return colourBySequence;
374   }
375
376   /**
377    * Called when the binding thinks the UI needs to be refreshed after a
378    * structure viewer state change. This could be because structures were
379    * loaded, or because an error has occurred. Default does nothing, override as
380    * required.
381    */
382   public void refreshGUI()
383   {
384   }
385
386   /**
387    * Instruct the Jalview binding to update the pdbentries vector if necessary
388    * prior to matching the jmol view's contents to the list of structure files
389    * Jalview knows about. By default does nothing, override as required.
390    */
391   public void refreshPdbEntries()
392   {
393   }
394
395   public void setColourBySequence(boolean colourBySequence)
396   {
397     this.colourBySequence = colourBySequence;
398   }
399
400   protected void addSequenceAndChain(int pe, SequenceI[] seq,
401           String[] tchain)
402   {
403     if (pe < 0 || pe >= getPdbCount())
404     {
405       throw new Error(MessageManager.formatMessage(
406               "error.implementation_error_no_pdbentry_from_index",
407               new Object[]
408               { Integer.valueOf(pe).toString() }));
409     }
410     final String nullChain = "TheNullChain";
411     List<SequenceI> s = new ArrayList<>();
412     List<String> c = new ArrayList<>();
413     if (getChains() == null)
414     {
415       setChains(new String[getPdbCount()][]);
416     }
417     if (getSequence()[pe] != null)
418     {
419       for (int i = 0; i < getSequence()[pe].length; i++)
420       {
421         s.add(getSequence()[pe][i]);
422         if (getChains()[pe] != null)
423         {
424           if (i < getChains()[pe].length)
425           {
426             c.add(getChains()[pe][i]);
427           }
428           else
429           {
430             c.add(nullChain);
431           }
432         }
433         else
434         {
435           if (tchain != null && tchain.length > 0)
436           {
437             c.add(nullChain);
438           }
439         }
440       }
441     }
442     for (int i = 0; i < seq.length; i++)
443     {
444       if (!s.contains(seq[i]))
445       {
446         s.add(seq[i]);
447         if (tchain != null && i < tchain.length)
448         {
449           c.add(tchain[i] == null ? nullChain : tchain[i]);
450         }
451       }
452     }
453     SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
454     getSequence()[pe] = tmp;
455     if (c.size() > 0)
456     {
457       String[] tch = c.toArray(new String[c.size()]);
458       for (int i = 0; i < tch.length; i++)
459       {
460         if (tch[i] == nullChain)
461         {
462           tch[i] = null;
463         }
464       }
465       getChains()[pe] = tch;
466     }
467     else
468     {
469       getChains()[pe] = null;
470     }
471   }
472
473   /**
474    * add structures and any known sequence associations
475    * 
476    * @returns the pdb entries added to the current set.
477    */
478   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
479           SequenceI[][] seq, String[][] chns)
480   {
481     List<PDBEntry> v = new ArrayList<>();
482     List<int[]> rtn = new ArrayList<>();
483     for (int i = 0; i < getPdbCount(); i++)
484     {
485       v.add(getPdbEntry(i));
486     }
487     for (int i = 0; i < pdbe.length; i++)
488     {
489       int r = v.indexOf(pdbe[i]);
490       if (r == -1 || r >= getPdbCount())
491       {
492         rtn.add(new int[] { v.size(), i });
493         v.add(pdbe[i]);
494       }
495       else
496       {
497         // just make sure the sequence/chain entries are all up to date
498         addSequenceAndChain(r, seq[i], chns[i]);
499       }
500     }
501     pdbe = v.toArray(new PDBEntry[v.size()]);
502     setPdbentry(pdbe);
503     if (rtn.size() > 0)
504     {
505       // expand the tied sequence[] and string[] arrays
506       SequenceI[][] sqs = new SequenceI[getPdbCount()][];
507       String[][] sch = new String[getPdbCount()][];
508       System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
509       System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
510       setSequence(sqs);
511       setChains(sch);
512       pdbe = new PDBEntry[rtn.size()];
513       for (int r = 0; r < pdbe.length; r++)
514       {
515         int[] stri = (rtn.get(r));
516         // record the pdb file as a new addition
517         pdbe[r] = getPdbEntry(stri[0]);
518         // and add the new sequence/chain entries
519         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
520       }
521     }
522     else
523     {
524       pdbe = null;
525     }
526     return pdbe;
527   }
528
529   /**
530    * Add sequences to the pe'th pdbentry's sequence set.
531    * 
532    * @param pe
533    * @param seq
534    */
535   public void addSequence(int pe, SequenceI[] seq)
536   {
537     addSequenceAndChain(pe, seq, null);
538   }
539
540   /**
541    * add the given sequences to the mapping scope for the given pdb file handle
542    * 
543    * @param pdbFile
544    *          - pdbFile identifier
545    * @param seq
546    *          - set of sequences it can be mapped to
547    */
548   public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
549   {
550     for (int pe = 0; pe < getPdbCount(); pe++)
551     {
552       if (getPdbEntry(pe).getFile().equals(pdbFile))
553       {
554         addSequence(pe, seq);
555       }
556     }
557   }
558
559   @Override
560   public abstract void highlightAtoms(List<AtomSpec> atoms);
561
562   protected boolean isNucleotide()
563   {
564     return this.nucleotide;
565   }
566
567   /**
568    * Returns a readable description of all mappings for the wrapped pdbfile to
569    * any mapped sequences
570    * 
571    * @param pdbfile
572    * @param seqs
573    * @return
574    */
575   public String printMappings()
576   {
577     if (pdbEntry == null)
578     {
579       return "";
580     }
581     StringBuilder sb = new StringBuilder(128);
582     for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
583     {
584       String pdbfile = getPdbEntry(pdbe).getFile();
585       List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
586       sb.append(getSsm().printMappings(pdbfile, seqs));
587     }
588     return sb.toString();
589   }
590
591   /**
592    * Returns the mapped structure position for a given aligned column of a given
593    * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
594    * not mapped to structure.
595    * 
596    * @param seq
597    * @param alignedPos
598    * @param mapping
599    * @return
600    */
601   protected int getMappedPosition(SequenceI seq, int alignedPos,
602           StructureMapping mapping)
603   {
604     if (alignedPos >= seq.getLength())
605     {
606       return -1;
607     }
608
609     if (Comparison.isGap(seq.getCharAt(alignedPos)))
610     {
611       return -1;
612     }
613     int seqPos = seq.findPosition(alignedPos);
614     int pos = mapping.getPDBResNum(seqPos);
615     return pos;
616   }
617
618   /**
619    * Helper method to identify residues that can participate in a structure
620    * superposition command. For each structure, identify a sequence in the
621    * alignment which is mapped to the structure. Identify non-gapped columns in
622    * the sequence which have a mapping to a residue in the structure. Returns
623    * the index of the first structure that has a mapping to the alignment.
624    * 
625    * @param alignment
626    *          the sequence alignment which is the basis of structure
627    *          superposition
628    * @param matched
629    *          a BitSet, where bit j is set to indicate that every structure has
630    *          a mapped residue present in column j (so the column can
631    *          participate in structure alignment)
632    * @param structures
633    *          an array of data beans corresponding to pdb file index
634    * @return
635    */
636   protected int findSuperposableResidues(AlignmentI alignment,
637           BitSet matched, SuperposeData[] structures)
638   {
639     int refStructure = -1;
640     String[] files = getStructureFiles();
641     if (files == null)
642     {
643       return -1;
644     }
645     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
646     {
647       StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
648       int lastPos = -1;
649
650       /*
651        * Find the first mapped sequence (if any) for this PDB entry which is in
652        * the alignment
653        */
654       final int seqCountForPdbFile = getSequence()[pdbfnum].length;
655       for (int s = 0; s < seqCountForPdbFile; s++)
656       {
657         for (StructureMapping mapping : mappings)
658         {
659           final SequenceI theSequence = getSequence()[pdbfnum][s];
660           if (mapping.getSequence() == theSequence
661                   && alignment.findIndex(theSequence) > -1)
662           {
663             if (refStructure < 0)
664             {
665               refStructure = pdbfnum;
666             }
667             for (int r = 0; r < alignment.getWidth(); r++)
668             {
669               if (!matched.get(r))
670               {
671                 continue;
672               }
673               int pos = getMappedPosition(theSequence, r, mapping);
674               if (pos < 1 || pos == lastPos)
675               {
676                 matched.clear(r);
677                 continue;
678               }
679               lastPos = pos;
680               structures[pdbfnum].pdbResNo[r] = pos;
681             }
682             String chain = mapping.getChain();
683             if (chain != null && chain.trim().length() > 0)
684             {
685               structures[pdbfnum].chain = chain;
686             }
687             structures[pdbfnum].pdbId = mapping.getPdbId();
688             structures[pdbfnum].isRna = theSequence.getRNA() != null;
689
690             /*
691              * move on to next pdb file (ignore sequences for other chains
692              * for the same structure)
693              */
694             s = seqCountForPdbFile;
695             break;
696           }
697         }
698       }
699     }
700     return refStructure;
701   }
702
703   /**
704    * Returns true if the structure viewer has loaded all of the files of
705    * interest (identified by the file mapping having been set up), or false if
706    * any are still not loaded after a timeout interval.
707    * 
708    * @param files
709    */
710   protected boolean waitForFileLoad(String[] files)
711   {
712     /*
713      * give up after 10 secs plus 1 sec per file
714      */
715     long starttime = System.currentTimeMillis();
716     long endTime = 10000 + 1000 * files.length + starttime;
717     String notLoaded = null;
718
719     boolean waiting = true;
720     while (waiting && System.currentTimeMillis() < endTime)
721     {
722       waiting = false;
723       for (String file : files)
724       {
725         notLoaded = file;
726         if (file == null)
727         {
728           continue;
729         }
730         try
731         {
732           StructureMapping[] sm = getSsm().getMapping(file);
733           if (sm == null || sm.length == 0)
734           {
735             waiting = true;
736           }
737         } catch (Throwable x)
738         {
739           waiting = true;
740         }
741       }
742     }
743
744     if (waiting)
745     {
746       System.err.println(
747               "Timed out waiting for structure viewer to load file "
748                       + notLoaded);
749       return false;
750     }
751     return true;
752   }
753
754   @Override
755   public boolean isListeningFor(SequenceI seq)
756   {
757     if (sequence != null)
758     {
759       for (SequenceI[] seqs : sequence)
760       {
761         if (seqs != null)
762         {
763           for (SequenceI s : seqs)
764           {
765             if (s == seq || (s.getDatasetSequence() != null
766                     && s.getDatasetSequence() == seq.getDatasetSequence()))
767             {
768               return true;
769             }
770           }
771         }
772       }
773     }
774     return false;
775   }
776
777   public boolean isFinishedInit()
778   {
779     return finishedInit;
780   }
781
782   public void setFinishedInit(boolean fi)
783   {
784     this.finishedInit = fi;
785   }
786
787   /**
788    * Returns a list of chains mapped in this viewer, formatted as
789    * "pdbid:chainCode"
790    * 
791    * @return
792    */
793   public List<String> getChainNames()
794   {
795     return chainNames;
796   }
797
798   /**
799    * Returns the Jalview panel hosting the structure viewer (if any)
800    * 
801    * @return
802    */
803   public JalviewStructureDisplayI getViewer()
804   {
805     return viewer;
806   }
807
808   public void setViewer(JalviewStructureDisplayI v)
809   {
810     viewer = v;
811   }
812
813   /**
814    * Constructs and sends a command to align structures against a reference
815    * structure, based on one or more sequence alignments. May optionally return
816    * an error or warning message for the alignment command.
817    * 
818    * @param alignments
819    *          an array of alignments to process
820    * @param structureIndices
821    *          an array of corresponding reference structures (index into pdb
822    *          file array); if a negative value is passed, the first PDB file
823    *          mapped to an alignment sequence is used as the reference for
824    *          superposition
825    * @param hiddenCols
826    *          an array of corresponding hidden columns for each alignment
827    * @return
828    */
829   public abstract String superposeStructures(AlignmentI[] alignments,
830           int[] structureIndices, HiddenColumns[] hiddenCols);
831
832   /**
833    * returns the current sequenceRenderer that should be used to colour the
834    * structures
835    * 
836    * @param alignment
837    * 
838    * @return
839    */
840   public abstract SequenceRenderer getSequenceRenderer(
841           AlignmentViewPanel alignment);
842
843   /**
844    * Sends a command to the structure viewer to colour each chain with a
845    * distinct colour (to the extent supported by the viewer)
846    */
847   public void colourByChain()
848   {
849     colourBySequence = false;
850
851     // TODO: JAL-628 colour chains distinctly across all visible models
852
853     executeCommand(commandGenerator.colourByChain(), false,
854             COLOURING_STRUCTURES);
855   }
856
857   /**
858    * Sends a command to the structure viewer to colour each chain with a
859    * distinct colour (to the extent supported by the viewer)
860    */
861   public void colourByCharge()
862   {
863     colourBySequence = false;
864
865     executeCommand(commandGenerator.colourByCharge(), false,
866             COLOURING_STRUCTURES);
867   }
868
869   /**
870    * Sends a command to the structure to apply a colour scheme (defined in
871    * Jalview but not necessarily applied to the alignment), which defines a
872    * colour per residue letter. More complex schemes (e.g. that depend on
873    * consensus) cannot be used here and are ignored.
874    * 
875    * @param cs
876    */
877   public void colourByJalviewColourScheme(ColourSchemeI cs)
878   {
879     colourBySequence = false;
880
881     if (cs == null || !cs.isSimple())
882     {
883       return;
884     }
885     
886     /*
887      * build a map of {Residue3LetterCode, Color}
888      */
889     Map<String, Color> colours = new HashMap<>();
890     List<String> residues = ResidueProperties.getResidues(isNucleotide(),
891             false);
892     for (String resName : residues)
893     {
894       char res = resName.length() == 3
895               ? ResidueProperties.getSingleCharacterCode(resName)
896               : resName.charAt(0);
897       Color colour = cs.findColour(res, 0, null, null, 0f);
898       colours.put(resName, colour);
899     }
900
901     /*
902      * pass to the command constructor, and send the command
903      */
904     String cmd = commandGenerator.colourByResidues(colours);
905     executeCommand(cmd, false, COLOURING_STRUCTURES);
906   }
907
908   public void setBackgroundColour(Color col)
909   {
910     String cmd = commandGenerator.setBackgroundColour(col);
911     executeCommand(cmd, false, null);
912   }
913
914   /**
915    * Sends one command to the structure viewer. If {@code getReply} is true, the
916    * command is sent synchronously, otherwise in a deferred thread.
917    * <p>
918    * If a progress message is supplied, this is displayed before command
919    * execution, and removed afterwards.
920    * 
921    * @param cmd
922    * @param getReply
923    * @param msg
924    * @return
925    */
926   private List<String> executeCommand(String cmd, boolean getReply,
927           String msg)
928   {
929     if (getReply)
930     {
931       return executeSynchronous(cmd, msg, getReply);
932     }
933     else
934     {
935       executeAsynchronous(cmd, msg);
936       return null;
937     }
938   }
939
940   /**
941    * Sends the command in the current thread. If a message is supplied, this is
942    * shown before the thread is started, and removed when it completes. May
943    * return a reply to the command if requested.
944    * 
945    * @param cmd
946    * @param msg
947    * @param getReply
948    * @return
949    */
950   private List<String> executeSynchronous(String cmd, String msg, boolean getReply)
951   {
952     final JalviewStructureDisplayI theViewer = getViewer();
953     final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
954     try
955     {
956       return executeCommand(cmd, getReply);
957     } finally
958     {
959       if (msg != null)
960       {
961         theViewer.stopProgressBar(null, handle);
962       }
963     }
964   }
965
966   /**
967    * Sends the command in a separate thread. If a message is supplied, this is
968    * shown before the thread is started, and removed when it completes. No value
969    * is returned.
970    * 
971    * @param cmd
972    * @param msg
973    */
974   private void executeAsynchronous(String cmd, String msg)
975   {
976     final JalviewStructureDisplayI theViewer = getViewer();
977     final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
978
979     SwingUtilities.invokeLater(new Runnable()
980     {
981       @Override
982       public void run()
983       {
984         try
985         {
986           executeCommand(cmd, false);
987         } finally
988         {
989           if (msg != null)
990           {
991             theViewer.stopProgressBar(null, handle);
992           }
993         }
994       }
995     });
996   }
997
998   protected abstract List<String> executeCommand(String command,
999           boolean getReply);
1000
1001   protected List<String> executeCommands(boolean getReply,
1002           String... commands)
1003   {
1004     List<String> response = null;
1005     for (String cmd : commands)
1006     {
1007       response = executeCommand(cmd, getReply);
1008     }
1009     return response;
1010   }
1011
1012   /**
1013    * colour any structures associated with sequences in the given alignment
1014    * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
1015    * if colourBySequence is enabled.
1016    */
1017   public void colourBySequence(AlignmentViewPanel alignmentv)
1018   {
1019     if (!colourBySequence || !isLoadingFinished())
1020     {
1021       return;
1022     }
1023     if (getSsm() == null)
1024     {
1025       return;
1026     }
1027     String[] files = getStructureFiles();
1028
1029     SequenceRenderer sr = getSequenceRenderer(alignmentv);
1030
1031     String[] colourBySequenceCommands = commandGenerator
1032             .colourBySequence(getSsm(), files, getSequence(), sr,
1033                     alignmentv);
1034     executeCommands(false, colourBySequenceCommands);
1035   }
1036
1037   /**
1038    * Centre the display in the structure viewer
1039    */
1040   public void focusView()
1041   {
1042     executeCommand(commandGenerator.focusView(), false);
1043   }
1044
1045   /**
1046    * Generates and executes a command to show only specified chains in the
1047    * structure viewer. The list of chains to show should contain entries
1048    * formatted as "pdbid:chaincode".
1049    * 
1050    * @param toShow
1051    */
1052   public void showChains(List<String> toShow)
1053   {
1054     // todo or reformat toShow list entries as modelNo:pdbId:chainCode ?
1055
1056     /*
1057      * Reformat the pdbid:chainCode values as modelNo:chainCode
1058      * since this is what is needed to construct the viewer command
1059      * todo: find a less messy way to do this
1060      */
1061     List<String> showThese = new ArrayList<>();
1062     for (String chainId : toShow)
1063     {
1064       String[] tokens = chainId.split("\\:");
1065       if (tokens.length == 2)
1066       {
1067         String pdbFile = getFileForChain(chainId);
1068         int modelNo = getModelNoForFile(pdbFile);
1069         String model = modelNo == -1 ? "" : String.valueOf(modelNo);
1070         showThese.add(model + ":" + tokens[1]);
1071       }
1072     }
1073     executeCommand(commandGenerator.showChains(showThese), false);
1074   }
1075
1076   /**
1077    * Answers the structure viewer's model number given a PDB file name. Returns
1078    * -1 if model number is not found.
1079    * 
1080    * @param chainId
1081    * @return
1082    */
1083   protected abstract int getModelNoForFile(String chainId);
1084
1085   public boolean hasFileLoadingError()
1086   {
1087     return fileLoadingError != null && fileLoadingError.length() > 0;
1088   }
1089
1090   /**
1091    * Returns the FeatureRenderer for the given alignment view, or null if
1092    * feature display is turned off in the view.
1093    * 
1094    * @param avp
1095    * @return
1096    */
1097   public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
1098   {
1099     AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
1100             : avp;
1101     return ap.getAlignViewport().isShowSequenceFeatures()
1102             ? ap.getFeatureRenderer()
1103             : null;
1104   }
1105
1106   protected void setStructureCommands(StructureCommandsI cmd)
1107   {
1108     commandGenerator = cmd;
1109   }
1110
1111   /**
1112    * Records association of one chain id (formatted as "pdbid:chainCode") with
1113    * the corresponding PDB file name
1114    * 
1115    * @param chainId
1116    * @param fileName
1117    */
1118   public void addChainFile(String chainId, String fileName)
1119   {
1120     chainFile.put(chainId, fileName);
1121   }
1122
1123   /**
1124    * Returns the PDB filename for the given chain id (formatted as
1125    * "pdbid:chainCode"), or null if not found
1126    * 
1127    * @param chainId
1128    * @return
1129    */
1130   protected String getFileForChain(String chainId)
1131   {
1132     return chainFile.get(chainId);
1133   }
1134
1135   @Override
1136   public void updateColours(Object source)
1137   {
1138     AlignmentViewPanel ap = (AlignmentViewPanel) source;
1139     // ignore events from panels not used to colour this view
1140     if (!getViewer().isUsedForColourBy(ap))
1141     {
1142       return;
1143     }
1144     if (!isLoadingFromArchive())
1145     {
1146       colourBySequence(ap);
1147     }
1148   }
1149 }