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