JAL-3148 show/hide features on structure independent of alignment
[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.SequenceRendererI;
25 import jalview.api.StructureSelectionManagerProvider;
26 import jalview.api.structures.JalviewStructureDisplayI;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.HiddenColumns;
29 import jalview.datamodel.PDBEntry;
30 import jalview.datamodel.SequenceI;
31 import jalview.gui.AlignmentPanel;
32 import jalview.gui.SequenceRenderer;
33 import jalview.io.DataSourceType;
34 import jalview.schemes.ColourSchemeI;
35 import jalview.structure.AtomSpec;
36 import jalview.structure.StructureListener;
37 import jalview.structure.StructureMapping;
38 import jalview.structure.StructureMappingcommandSet;
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.List;
48
49 import org.springframework.web.filter.ShallowEtagHeaderFilter;
50
51 /**
52  * 
53  * A base class to hold common function for protein structure model binding.
54  * Initial version created by refactoring JMol and Chimera binding models, but
55  * other structure viewers could in principle be accommodated in future.
56  * 
57  * @author gmcarstairs
58  *
59  */
60 public abstract class AAStructureBindingModel
61         extends SequenceStructureBindingModel
62         implements StructureListener, StructureSelectionManagerProvider
63 {
64   public enum ColourBy 
65   {
66     Sequence, Chain, ChargeAndCysteine, Jalview, Viewer 
67   }
68
69   /*
70    * selected colour menu option
71    */
72   private ColourBy colourBy = ColourBy.Sequence;
73
74   /*
75    * selected colour scheme, if a Jalview colour scheme is selected
76    */
77   private ColourSchemeI colourScheme;
78   
79   /*
80    * flag for whether to include feature colouring, if using a Jalview colour
81    * scheme (independently of whether features are shown on alignment)
82    */
83   private boolean showFeatures;
84   
85   private StructureSelectionManager ssm;
86
87   /*
88    * distinct PDB entries (pdb files) associated
89    * with sequences
90    */
91   private PDBEntry[] pdbEntry;
92
93   /*
94    * sequences mapped to each pdbentry
95    */
96   private SequenceI[][] sequence;
97
98   /*
99    * array of target chains for sequences - tied to pdbentry and sequence[]
100    */
101   private String[][] chains;
102
103   /*
104    * datasource protocol for access to PDBEntrylatest
105    */
106   DataSourceType protocol = null;
107   
108   private boolean nucleotide;
109
110   private boolean finishedInit = false;
111
112   /**
113    * current set of model filenames loaded in the Jmol instance
114    */
115   protected String[] modelFileNames = null;
116
117   public String fileLoadingError;
118
119   /**
120    * Data bean class to simplify parameterisation in superposeStructures
121    */
122   protected class SuperposeData
123   {
124     /**
125      * Constructor with alignment width argument
126      * 
127      * @param width
128      */
129     public SuperposeData(int width)
130     {
131       pdbResNo = new int[width];
132     }
133
134     public String filename;
135
136     public String pdbId;
137
138     public String chain = "";
139
140     public boolean isRna;
141
142     /*
143      * The pdb residue number (if any) mapped to each column of the alignment
144      */
145     public int[] pdbResNo;
146   }
147
148   /**
149    * Constructor
150    * 
151    * @param ssm
152    * @param seqs
153    */
154   public AAStructureBindingModel(StructureSelectionManager ssm,
155           SequenceI[][] seqs)
156   {
157     this.ssm = ssm;
158     this.sequence = seqs;
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     resolveChains();
179   }
180
181   private boolean resolveChains()
182   {
183     /**
184      * final count of chain mappings discovered
185      */
186     int chainmaps = 0;
187     // JBPNote: JAL-2693 - this should be a list of chain mappings per
188     // [pdbentry][sequence]
189     String[][] newchains = new String[pdbEntry.length][];
190     int pe = 0;
191     for (PDBEntry pdb : pdbEntry)
192     {
193       SequenceI[] seqsForPdb = sequence[pe];
194       if (seqsForPdb != null)
195       {
196         newchains[pe] = new String[seqsForPdb.length];
197         int se = 0;
198         for (SequenceI asq : seqsForPdb)
199         {
200           String chain = (chains != null && chains[pe] != null)
201                   ? chains[pe][se]
202                   : null;
203           SequenceI sq = (asq.getDatasetSequence() == null) ? asq
204                   : asq.getDatasetSequence();
205           if (sq.getAllPDBEntries() != null)
206           {
207             for (PDBEntry pdbentry : sq.getAllPDBEntries())
208             {
209               if (pdb.getFile() != null && pdbentry.getFile() != null
210                       && pdb.getFile().equals(pdbentry.getFile()))
211               {
212                 String chaincode = pdbentry.getChainCode();
213                 if (chaincode != null && chaincode.length() > 0)
214                 {
215                   chain = chaincode;
216                   chainmaps++;
217                   break;
218                 }
219               }
220             }
221           }
222           newchains[pe][se] = chain;
223           se++;
224         }
225         pe++;
226       }
227     }
228
229     chains = newchains;
230     return chainmaps > 0;
231   }
232   public StructureSelectionManager getSsm()
233   {
234     return ssm;
235   }
236
237   /**
238    * Returns the i'th PDBEntry (or null)
239    * 
240    * @param i
241    * @return
242    */
243   public PDBEntry getPdbEntry(int i)
244   {
245     return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
246   }
247
248   /**
249    * Answers true if this binding includes the given PDB id, else false
250    * 
251    * @param pdbId
252    * @return
253    */
254   public boolean hasPdbId(String pdbId)
255   {
256     if (pdbEntry != null)
257     {
258       for (PDBEntry pdb : pdbEntry)
259       {
260         if (pdb.getId().equals(pdbId))
261         {
262           return true;
263         }
264       }
265     }
266     return false;
267   }
268
269   /**
270    * Returns the number of modelled PDB file entries.
271    * 
272    * @return
273    */
274   public int getPdbCount()
275   {
276     return pdbEntry == null ? 0 : pdbEntry.length;
277   }
278
279   public SequenceI[][] getSequence()
280   {
281     return sequence;
282   }
283
284   public String[][] getChains()
285   {
286     return chains;
287   }
288
289   public DataSourceType getProtocol()
290   {
291     return protocol;
292   }
293
294   // TODO may remove this if calling methods can be pulled up here
295   protected void setPdbentry(PDBEntry[] pdbentry)
296   {
297     this.pdbEntry = pdbentry;
298   }
299
300   protected void setSequence(SequenceI[][] sequence)
301   {
302     this.sequence = sequence;
303   }
304
305   protected void setChains(String[][] chains)
306   {
307     this.chains = chains;
308   }
309
310   /**
311    * Construct a title string for the viewer window based on the data Jalview
312    * knows about
313    * 
314    * @param viewerName
315    *          TODO
316    * @param verbose
317    * 
318    * @return
319    */
320   public String getViewerTitle(String viewerName, boolean verbose)
321   {
322     if (getSequence() == null || getSequence().length < 1
323             || getPdbCount() < 1 || getSequence()[0].length < 1)
324     {
325       return ("Jalview " + viewerName + " Window");
326     }
327     // TODO: give a more informative title when multiple structures are
328     // displayed.
329     StringBuilder title = new StringBuilder(64);
330     final PDBEntry pdbe = getPdbEntry(0);
331     title.append(viewerName + " view for " + getSequence()[0][0].getName()
332             + ":" + pdbe.getId());
333
334     if (verbose)
335     {
336       String method = (String) pdbe.getProperty("method");
337       if (method != null)
338       {
339         title.append(" Method: ").append(method);
340       }
341       String chain = (String) pdbe.getProperty("chains");
342       if (chain != null)
343       {
344         title.append(" Chain:").append(chain);
345       }
346     }
347     return title.toString();
348   }
349
350   /**
351    * Called by after closeViewer is called, to release any resources and
352    * references so they can be garbage collected. Override if needed.
353    */
354   protected void releaseUIResources()
355   {
356
357   }
358
359   public void setColourBy(ColourBy option)
360   {
361         colourBy = option;
362   }
363   
364   public boolean isColourBySequence()
365   {
366     return colourBy == ColourBy.Sequence;
367   }
368   
369   protected boolean isJalviewColourScheme()
370   {
371         return colourBy == ColourBy.Jalview;
372   }
373
374   protected void addSequenceAndChain(int pe, SequenceI[] seq,
375           String[] tchain)
376   {
377     if (pe < 0 || pe >= getPdbCount())
378     {
379       throw new Error(MessageManager.formatMessage(
380               "error.implementation_error_no_pdbentry_from_index",
381               new Object[]
382               { Integer.valueOf(pe).toString() }));
383     }
384     final String nullChain = "TheNullChain";
385     List<SequenceI> s = new ArrayList<SequenceI>();
386     List<String> c = new ArrayList<String>();
387     if (getChains() == null)
388     {
389       setChains(new String[getPdbCount()][]);
390     }
391     if (getSequence()[pe] != null)
392     {
393       for (int i = 0; i < getSequence()[pe].length; i++)
394       {
395         s.add(getSequence()[pe][i]);
396         if (getChains()[pe] != null)
397         {
398           if (i < getChains()[pe].length)
399           {
400             c.add(getChains()[pe][i]);
401           }
402           else
403           {
404             c.add(nullChain);
405           }
406         }
407         else
408         {
409           if (tchain != null && tchain.length > 0)
410           {
411             c.add(nullChain);
412           }
413         }
414       }
415     }
416     for (int i = 0; i < seq.length; i++)
417     {
418       if (!s.contains(seq[i]))
419       {
420         s.add(seq[i]);
421         if (tchain != null && i < tchain.length)
422         {
423           c.add(tchain[i] == null ? nullChain : tchain[i]);
424         }
425       }
426     }
427     SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
428     getSequence()[pe] = tmp;
429     if (c.size() > 0)
430     {
431       String[] tch = c.toArray(new String[c.size()]);
432       for (int i = 0; i < tch.length; i++)
433       {
434         if (tch[i] == nullChain)
435         {
436           tch[i] = null;
437         }
438       }
439       getChains()[pe] = tch;
440     }
441     else
442     {
443       getChains()[pe] = null;
444     }
445   }
446
447   /**
448    * add structures and any known sequence associations
449    * 
450    * @returns the pdb entries added to the current set.
451    */
452   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
453           SequenceI[][] seq, String[][] chns)
454   {
455     List<PDBEntry> v = new ArrayList<PDBEntry>();
456     List<int[]> rtn = new ArrayList<int[]>();
457     for (int i = 0; i < getPdbCount(); i++)
458     {
459       v.add(getPdbEntry(i));
460     }
461     for (int i = 0; i < pdbe.length; i++)
462     {
463       int r = v.indexOf(pdbe[i]);
464       if (r == -1 || r >= getPdbCount())
465       {
466         rtn.add(new int[] { v.size(), i });
467         v.add(pdbe[i]);
468       }
469       else
470       {
471         // just make sure the sequence/chain entries are all up to date
472         addSequenceAndChain(r, seq[i], chns[i]);
473       }
474     }
475     pdbe = v.toArray(new PDBEntry[v.size()]);
476     setPdbentry(pdbe);
477     if (rtn.size() > 0)
478     {
479       // expand the tied sequence[] and string[] arrays
480       SequenceI[][] sqs = new SequenceI[getPdbCount()][];
481       String[][] sch = new String[getPdbCount()][];
482       System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
483       System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
484       setSequence(sqs);
485       setChains(sch);
486       pdbe = new PDBEntry[rtn.size()];
487       for (int r = 0; r < pdbe.length; r++)
488       {
489         int[] stri = (rtn.get(r));
490         // record the pdb file as a new addition
491         pdbe[r] = getPdbEntry(stri[0]);
492         // and add the new sequence/chain entries
493         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
494       }
495     }
496     else
497     {
498       pdbe = null;
499     }
500     return pdbe;
501   }
502
503   /**
504    * Add sequences to the pe'th pdbentry's sequence set.
505    * 
506    * @param pe
507    * @param seq
508    */
509   public void addSequence(int pe, SequenceI[] seq)
510   {
511     addSequenceAndChain(pe, seq, null);
512   }
513
514   /**
515    * add the given sequences to the mapping scope for the given pdb file handle
516    * 
517    * @param pdbFile
518    *          - pdbFile identifier
519    * @param seq
520    *          - set of sequences it can be mapped to
521    */
522   public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
523   {
524     for (int pe = 0; pe < getPdbCount(); pe++)
525     {
526       if (getPdbEntry(pe).getFile().equals(pdbFile))
527       {
528         addSequence(pe, seq);
529       }
530     }
531   }
532
533   @Override
534   public abstract void highlightAtoms(List<AtomSpec> atoms);
535
536   protected boolean isNucleotide()
537   {
538     return this.nucleotide;
539   }
540
541   /**
542    * Returns a readable description of all mappings for the wrapped pdbfile to
543    * any mapped sequences
544    * 
545    * @param pdbfile
546    * @param seqs
547    * @return
548    */
549   public String printMappings()
550   {
551     if (pdbEntry == null)
552     {
553       return "";
554     }
555     StringBuilder sb = new StringBuilder(128);
556     for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
557     {
558       String pdbfile = getPdbEntry(pdbe).getFile();
559       List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
560       sb.append(getSsm().printMappings(pdbfile, seqs));
561     }
562     return sb.toString();
563   }
564
565   /**
566    * Returns the mapped structure position for a given aligned column of a given
567    * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
568    * not mapped to structure.
569    * 
570    * @param seq
571    * @param alignedPos
572    * @param mapping
573    * @return
574    */
575   protected int getMappedPosition(SequenceI seq, int alignedPos,
576           StructureMapping mapping)
577   {
578     if (alignedPos >= seq.getLength())
579     {
580       return -1;
581     }
582
583     if (Comparison.isGap(seq.getCharAt(alignedPos)))
584     {
585       return -1;
586     }
587     int seqPos = seq.findPosition(alignedPos);
588     int pos = mapping.getPDBResNum(seqPos);
589     return pos;
590   }
591
592   /**
593    * Helper method to identify residues that can participate in a structure
594    * superposition command. For each structure, identify a sequence in the
595    * alignment which is mapped to the structure. Identify non-gapped columns in
596    * the sequence which have a mapping to a residue in the structure. Returns
597    * the index of the first structure that has a mapping to the alignment.
598    * 
599    * @param alignment
600    *          the sequence alignment which is the basis of structure
601    *          superposition
602    * @param matched
603    *          a BitSet, where bit j is set to indicate that every structure has
604    *          a mapped residue present in column j (so the column can
605    *          participate in structure alignment)
606    * @param structures
607    *          an array of data beans corresponding to pdb file index
608    * @return
609    */
610   protected int findSuperposableResidues(AlignmentI alignment,
611           BitSet matched, SuperposeData[] structures)
612   {
613     int refStructure = -1;
614     String[] files = getStructureFiles();
615     if (files == null)
616     {
617       return -1;
618     }
619     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
620     {
621       StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
622       int lastPos = -1;
623
624       /*
625        * Find the first mapped sequence (if any) for this PDB entry which is in
626        * the alignment
627        */
628       final int seqCountForPdbFile = getSequence()[pdbfnum].length;
629       for (int s = 0; s < seqCountForPdbFile; s++)
630       {
631         for (StructureMapping mapping : mappings)
632         {
633           final SequenceI theSequence = getSequence()[pdbfnum][s];
634           if (mapping.getSequence() == theSequence
635                   && alignment.findIndex(theSequence) > -1)
636           {
637             if (refStructure < 0)
638             {
639               refStructure = pdbfnum;
640             }
641             for (int r = 0; r < alignment.getWidth(); r++)
642             {
643               if (!matched.get(r))
644               {
645                 continue;
646               }
647               int pos = getMappedPosition(theSequence, r, mapping);
648               if (pos < 1 || pos == lastPos)
649               {
650                 matched.clear(r);
651                 continue;
652               }
653               lastPos = pos;
654               structures[pdbfnum].pdbResNo[r] = pos;
655             }
656             String chain = mapping.getChain();
657             if (chain != null && chain.trim().length() > 0)
658             {
659               structures[pdbfnum].chain = chain;
660             }
661             structures[pdbfnum].pdbId = mapping.getPdbId();
662             structures[pdbfnum].isRna = theSequence.getRNA() != null;
663
664             /*
665              * move on to next pdb file (ignore sequences for other chains
666              * for the same structure)
667              */
668             s = seqCountForPdbFile;
669             break;
670           }
671         }
672       }
673     }
674     return refStructure;
675   }
676
677   /**
678    * Returns true if the structure viewer has loaded all of the files of
679    * interest (identified by the file mapping having been set up), or false if
680    * any are still not loaded after a timeout interval.
681    * 
682    * @param files
683    */
684   protected boolean waitForFileLoad(String[] files)
685   {
686     /*
687      * give up after 10 secs plus 1 sec per file
688      */
689     long starttime = System.currentTimeMillis();
690     long endTime = 10000 + 1000 * files.length + starttime;
691     String notLoaded = null;
692
693     boolean waiting = true;
694     while (waiting && System.currentTimeMillis() < endTime)
695     {
696       waiting = false;
697       for (String file : files)
698       {
699         notLoaded = file;
700         if (file == null)
701         {
702           continue;
703         }
704         try
705         {
706           StructureMapping[] sm = getSsm().getMapping(file);
707           if (sm == null || sm.length == 0)
708           {
709             waiting = true;
710           }
711         } catch (Throwable x)
712         {
713           waiting = true;
714         }
715       }
716     }
717
718     if (waiting)
719     {
720       System.err.println(
721               "Timed out waiting for structure viewer to load file "
722                       + notLoaded);
723       return false;
724     }
725     return true;
726   }
727
728   @Override
729   public boolean isListeningFor(SequenceI seq)
730   {
731     if (sequence != null)
732     {
733       for (SequenceI[] seqs : sequence)
734       {
735         if (seqs != null)
736         {
737           for (SequenceI s : seqs)
738           {
739             if (s == seq || (s.getDatasetSequence() != null
740                     && s.getDatasetSequence() == seq.getDatasetSequence()))
741             {
742               return true;
743             }
744           }
745         }
746       }
747     }
748     return false;
749   }
750
751   public boolean isFinishedInit()
752   {
753     return finishedInit;
754   }
755
756   public void setFinishedInit(boolean fi)
757   {
758     this.finishedInit = fi;
759   }
760
761   /**
762    * Returns a list of chains mapped in this viewer.
763    * 
764    * @return
765    */
766   public abstract List<String> getChainNames();
767
768   /**
769    * Returns the Jalview panel hosting the structure viewer (if any)
770    * 
771    * @return
772    */
773   public JalviewStructureDisplayI getViewer()
774   {
775     return null;
776   }
777
778   /**
779    * Sets the selected colour scheme, possibly with reference to the given
780    * alignment view
781    * 
782    * @param cs
783    * @param ap
784    * @param showFeats
785    */
786   public void setJalviewColourScheme(ColourSchemeI cs, AlignmentViewPanel ap,
787           boolean showFeats)
788   {
789     colourBy = ColourBy.Jalview;
790     colourScheme = cs;
791     showFeatures = showFeats;
792     
793     if (!showFeats && (cs == null || cs.isSimple())) 
794     {
795       setSimpleColourScheme(cs);
796     } 
797     else
798     {
799       colourBySequence(ap, new SequenceRenderer(ap.getAlignViewport(), cs), showFeatures);
800     }
801   }
802
803   /**
804    * Sets a colour scheme which is determined solely by the residue at each 
805    * position
806    * 
807    * @param cs
808    */
809   protected abstract void setSimpleColourScheme(ColourSchemeI cs);
810   
811   /**
812    * Constructs and sends a command to align structures against a reference
813    * structure, based on one or more sequence alignments. May optionally return
814    * an error or warning message for the alignment command.
815    * 
816    * @param alignments
817    *          an array of alignments to process
818    * @param structureIndices
819    *          an array of corresponding reference structures (index into pdb
820    *          file array); if a negative value is passed, the first PDB file
821    *          mapped to an alignment sequence is used as the reference for
822    *          superposition
823    * @param hiddenCols
824    *          an array of corresponding hidden columns for each alignment
825    * @return
826    */
827   public abstract String superposeStructures(AlignmentI[] alignments,
828           int[] structureIndices, HiddenColumns[] hiddenCols);
829
830   public abstract void setBackgroundColour(Color col);
831
832   protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
833           String[] files, SequenceRendererI sr, AlignmentViewPanel avp, boolean showFeatures);
834
835   protected abstract void colourBySequence(
836           StructureMappingcommandSet[] colourBySequenceCommands);
837
838   public void colourByChain()
839   {
840     colourBy = ColourBy.Chain;
841   }
842
843   public void colourByCharge()
844   {
845     colourBy = ColourBy.ChargeAndCysteine;
846   }
847
848   public void colourBySequence(AlignmentViewPanel alignmentv, boolean showFeatures)
849   {
850     colourBySequence(alignmentv, alignmentv.getSequenceRenderer(), showFeatures);
851   }
852
853   /**
854    * Colours any structures associated with sequences in the given alignment view
855    * using the getFeatureRenderer() and getSequenceRenderer() renderers
856    */
857   public void colourBySequence(AlignmentViewPanel alignmentv, SequenceRendererI sr, boolean showFeats)
858   {
859     showFeatures = showFeats;
860     if (!isLoadingFinished())
861     {
862       return;
863     }
864     if (getSsm() == null)
865     {
866       return;
867     }
868     String[] files = getStructureFiles();
869
870     StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands(
871             files, sr, alignmentv, showFeatures);
872     colourBySequence(colourBySequenceCommands);
873   }
874
875   public boolean hasFileLoadingError()
876   {
877     return fileLoadingError != null && fileLoadingError.length() > 0;
878   }
879
880   public abstract jalview.api.FeatureRenderer getFeatureRenderer(
881           AlignmentViewPanel alignment);
882
883 @Override
884 public void updateColours(Object source) {
885     AlignmentPanel ap = (AlignmentPanel) source;
886     
887     /*
888      * ignore events from panels not used to colour this view
889      */
890     if (!getViewer().isUsedForColourBy(ap))
891     {
892       return;
893     }
894     
895     /*
896      * no need to update colours if structure colouring is not
897      * viewport dependent
898      */
899     if (!isColourBySequence() && !isJalviewColourScheme())
900     {
901       return;
902     }
903     if (!showFeatures && (colourScheme == null || colourScheme.isSimple()))
904     {
905       return;
906     }
907     
908     if (!isLoadingFromArchive())
909     {
910       if (isColourBySequence())
911       {
912         colourBySequence(ap, new SequenceRenderer(ap.getAlignViewport()), 
913                 showFeatures);
914       } 
915       else
916       {
917         colourBySequence(ap, new SequenceRenderer(ap.getAlignViewport(), 
918                 colourScheme), showFeatures);
919       }
920     }
921   }
922 }