4aec9d247e5ec49258976adeafece3c1222fd30f
[jalview.git] / src / jalview / viewmodel / AlignmentViewport.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.viewmodel;
22
23 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
24 import jalview.analysis.Conservation;
25 import jalview.analysis.TreeModel;
26 import jalview.api.AlignCalcManagerI;
27 import jalview.api.AlignCalcManagerI2;
28 import jalview.api.AlignCalcWorkerI;
29 import jalview.api.AlignExportSettingsI;
30 import jalview.api.AlignViewportI;
31 import jalview.api.AlignmentViewPanel;
32 import jalview.api.FeaturesDisplayedI;
33 import jalview.api.ViewStyleI;
34 import jalview.commands.CommandI;
35 import jalview.datamodel.AlignedCodonFrame;
36 import jalview.datamodel.AlignmentAnnotation;
37 import jalview.datamodel.AlignmentExportData;
38 import jalview.datamodel.AlignmentI;
39 import jalview.datamodel.AlignmentView;
40 import jalview.datamodel.Annotation;
41 import jalview.datamodel.ColumnSelection;
42 import jalview.datamodel.HiddenColumns;
43 import jalview.datamodel.HiddenSequences;
44 import jalview.datamodel.ProfilesI;
45 import jalview.datamodel.SearchResultsI;
46 import jalview.datamodel.Sequence;
47 import jalview.datamodel.SequenceCollectionI;
48 import jalview.datamodel.SequenceGroup;
49 import jalview.datamodel.SequenceI;
50 import jalview.renderer.ResidueShader;
51 import jalview.renderer.ResidueShaderI;
52 import jalview.schemes.ColourSchemeI;
53 import jalview.structure.CommandListener;
54 import jalview.structure.StructureSelectionManager;
55 import jalview.structure.VamsasSource;
56 import jalview.util.Comparison;
57 import jalview.util.MapList;
58 import jalview.util.MappingUtils;
59 import jalview.util.MessageManager;
60 import jalview.viewmodel.styles.ViewStyle;
61 import jalview.workers.AlignCalcManager;
62 import jalview.workers.AlignCalcManager2;
63 import jalview.workers.ComplementConsensusThread;
64 import jalview.workers.ConsensusThread;
65 import jalview.workers.InformationThread;
66 import jalview.workers.StrucConsensusThread;
67
68 import java.awt.Color;
69 import java.beans.PropertyChangeSupport;
70 import java.util.ArrayDeque;
71 import java.util.ArrayList;
72 import java.util.BitSet;
73 import java.util.Deque;
74 import java.util.HashMap;
75 import java.util.Hashtable;
76 import java.util.Iterator;
77 import java.util.List;
78 import java.util.Map;
79
80 /**
81  * base class holding visualization and analysis attributes and common logic for
82  * an active alignment view displayed in the GUI
83  * 
84  * @author jimp
85  * 
86  */
87 public abstract class AlignmentViewport
88         implements AlignViewportI, CommandListener, VamsasSource
89 {
90   public static final String PROPERTY_ALIGNMENT = "alignment";
91   public static final String PROPERTY_SEQUENCE = "sequence";
92
93   protected ViewportRanges ranges;
94
95   protected ViewStyleI viewStyle = new ViewStyle();
96
97   /**
98    * A viewport that hosts the cDna view of this (protein), or vice versa (if
99    * set).
100    */
101   AlignViewportI codingComplement = null;
102
103   FeaturesDisplayedI featuresDisplayed = null;
104
105   protected Deque<CommandI> historyList = new ArrayDeque<>();
106
107   protected Deque<CommandI> redoList = new ArrayDeque<>();
108
109   /**
110    * alignment displayed in the viewport. Please use get/setter
111    */
112   protected AlignmentI alignment;
113   
114   /*
115    * probably unused indicator that view is of a dataset rather than an
116    * alignment
117    */
118
119   protected boolean ignoreBelowBackGroundFrequencyCalculation = false;
120
121   protected boolean infoLetterHeight = false;
122
123   protected AlignmentAnnotation occupancy;
124   
125   /**
126    * results of alignment consensus analysis for visible portion of view
127    */
128   protected ProfilesI consensusProfiles;
129
130   /**
131    * HMM profile for the alignment
132    */
133   protected ProfilesI hmmProfiles;
134
135   public AlignmentViewport(AlignmentI al)
136   {
137     setAlignment(al);
138     ranges = new ViewportRanges(al);
139   }
140
141   /**
142    * @param name
143    * @see jalview.api.ViewStyleI#setFontName(java.lang.String)
144    */
145   @Override
146   public void setFontName(String name)
147   {
148     viewStyle.setFontName(name);
149   }
150
151   /**
152    * @param style
153    * @see jalview.api.ViewStyleI#setFontStyle(int)
154    */
155   @Override
156   public void setFontStyle(int style)
157   {
158     viewStyle.setFontStyle(style);
159   }
160
161   /**
162    * @param size
163    * @see jalview.api.ViewStyleI#setFontSize(int)
164    */
165   @Override
166   public void setFontSize(int size)
167   {
168     viewStyle.setFontSize(size);
169   }
170
171   /**
172    * @return
173    * @see jalview.api.ViewStyleI#getFontStyle()
174    */
175   @Override
176   public int getFontStyle()
177   {
178     return viewStyle.getFontStyle();
179   }
180
181   /**
182    * @return
183    * @see jalview.api.ViewStyleI#getFontName()
184    */
185   @Override
186   public String getFontName()
187   {
188     return viewStyle.getFontName();
189   }
190
191   /**
192    * @return
193    * @see jalview.api.ViewStyleI#getFontSize()
194    */
195   @Override
196   public int getFontSize()
197   {
198     return viewStyle.getFontSize();
199   }
200
201   /**
202    * @param upperCasebold
203    * @see jalview.api.ViewStyleI#setUpperCasebold(boolean)
204    */
205   @Override
206   public void setUpperCasebold(boolean upperCasebold)
207   {
208     viewStyle.setUpperCasebold(upperCasebold);
209   }
210
211   /**
212    * @return
213    * @see jalview.api.ViewStyleI#isUpperCasebold()
214    */
215   @Override
216   public boolean isUpperCasebold()
217   {
218     return viewStyle.isUpperCasebold();
219   }
220
221   /**
222    * @return
223    * @see jalview.api.ViewStyleI#isSeqNameItalics()
224    */
225   @Override
226   public boolean isSeqNameItalics()
227   {
228     return viewStyle.isSeqNameItalics();
229   }
230
231   /**
232    * @param colourByReferenceSeq
233    * @see jalview.api.ViewStyleI#setColourByReferenceSeq(boolean)
234    */
235   @Override
236   public void setColourByReferenceSeq(boolean colourByReferenceSeq)
237   {
238     viewStyle.setColourByReferenceSeq(colourByReferenceSeq);
239   }
240
241   /**
242    * @param b
243    * @see jalview.api.ViewStyleI#setColourAppliesToAllGroups(boolean)
244    */
245   @Override
246   public void setColourAppliesToAllGroups(boolean b)
247   {
248     viewStyle.setColourAppliesToAllGroups(b);
249   }
250
251   /**
252    * @return
253    * @see jalview.api.ViewStyleI#getColourAppliesToAllGroups()
254    */
255   @Override
256   public boolean getColourAppliesToAllGroups()
257   {
258     return viewStyle.getColourAppliesToAllGroups();
259   }
260
261   /**
262    * @return
263    * @see jalview.api.ViewStyleI#getAbovePIDThreshold()
264    */
265   @Override
266   public boolean getAbovePIDThreshold()
267   {
268     return viewStyle.getAbovePIDThreshold();
269   }
270
271   /**
272    * @param inc
273    * @see jalview.api.ViewStyleI#setIncrement(int)
274    */
275   @Override
276   public void setIncrement(int inc)
277   {
278     viewStyle.setIncrement(inc);
279   }
280
281   /**
282    * @return
283    * @see jalview.api.ViewStyleI#getIncrement()
284    */
285   @Override
286   public int getIncrement()
287   {
288     return viewStyle.getIncrement();
289   }
290
291   /**
292    * @param b
293    * @see jalview.api.ViewStyleI#setConservationSelected(boolean)
294    */
295   @Override
296   public void setConservationSelected(boolean b)
297   {
298     viewStyle.setConservationSelected(b);
299   }
300
301   /**
302    * @param show
303    * @see jalview.api.ViewStyleI#setShowHiddenMarkers(boolean)
304    */
305   @Override
306   public void setShowHiddenMarkers(boolean show)
307   {
308     viewStyle.setShowHiddenMarkers(show);
309   }
310
311   /**
312    * @return
313    * @see jalview.api.ViewStyleI#getShowHiddenMarkers()
314    */
315   @Override
316   public boolean getShowHiddenMarkers()
317   {
318     return viewStyle.getShowHiddenMarkers();
319   }
320
321   /**
322    * @param b
323    * @see jalview.api.ViewStyleI#setScaleRightWrapped(boolean)
324    */
325   @Override
326   public void setScaleRightWrapped(boolean b)
327   {
328     viewStyle.setScaleRightWrapped(b);
329   }
330
331   /**
332    * @param b
333    * @see jalview.api.ViewStyleI#setScaleLeftWrapped(boolean)
334    */
335   @Override
336   public void setScaleLeftWrapped(boolean b)
337   {
338     viewStyle.setScaleLeftWrapped(b);
339   }
340
341   /**
342    * @param b
343    * @see jalview.api.ViewStyleI#setScaleAboveWrapped(boolean)
344    */
345   @Override
346   public void setScaleAboveWrapped(boolean b)
347   {
348     viewStyle.setScaleAboveWrapped(b);
349   }
350
351   /**
352    * @return
353    * @see jalview.api.ViewStyleI#getScaleLeftWrapped()
354    */
355   @Override
356   public boolean getScaleLeftWrapped()
357   {
358     return viewStyle.getScaleLeftWrapped();
359   }
360
361   /**
362    * @return
363    * @see jalview.api.ViewStyleI#getScaleAboveWrapped()
364    */
365   @Override
366   public boolean getScaleAboveWrapped()
367   {
368     return viewStyle.getScaleAboveWrapped();
369   }
370
371   /**
372    * @return
373    * @see jalview.api.ViewStyleI#getScaleRightWrapped()
374    */
375   @Override
376   public boolean getScaleRightWrapped()
377   {
378     return viewStyle.getScaleRightWrapped();
379   }
380
381   /**
382    * @param b
383    * @see jalview.api.ViewStyleI#setAbovePIDThreshold(boolean)
384    */
385   @Override
386   public void setAbovePIDThreshold(boolean b)
387   {
388     viewStyle.setAbovePIDThreshold(b);
389   }
390
391   /**
392    * @param thresh
393    * @see jalview.api.ViewStyleI#setThreshold(int)
394    */
395   @Override
396   public void setThreshold(int thresh)
397   {
398     viewStyle.setThreshold(thresh);
399   }
400
401   /**
402    * @return
403    * @see jalview.api.ViewStyleI#getThreshold()
404    */
405   @Override
406   public int getThreshold()
407   {
408     return viewStyle.getThreshold();
409   }
410
411   /**
412    * @return
413    * @see jalview.api.ViewStyleI#getShowJVSuffix()
414    */
415   @Override
416   public boolean getShowJVSuffix()
417   {
418     return viewStyle.getShowJVSuffix();
419   }
420
421   /**
422    * @param b
423    * @see jalview.api.ViewStyleI#setShowJVSuffix(boolean)
424    */
425   @Override
426   public void setShowJVSuffix(boolean b)
427   {
428     viewStyle.setShowJVSuffix(b);
429   }
430
431   /**
432    * @param state
433    * @see jalview.api.ViewStyleI#setWrapAlignment(boolean)
434    */
435   @Override
436   public void setWrapAlignment(boolean state)
437   {
438     viewStyle.setWrapAlignment(state);
439     ranges.setWrappedMode(state);
440   }
441
442   /**
443    * @param state
444    * @see jalview.api.ViewStyleI#setShowText(boolean)
445    */
446   @Override
447   public void setShowText(boolean state)
448   {
449     viewStyle.setShowText(state);
450   }
451
452   /**
453    * @param state
454    * @see jalview.api.ViewStyleI#setRenderGaps(boolean)
455    */
456   @Override
457   public void setRenderGaps(boolean state)
458   {
459     viewStyle.setRenderGaps(state);
460   }
461
462   /**
463    * @return
464    * @see jalview.api.ViewStyleI#getColourText()
465    */
466   @Override
467   public boolean getColourText()
468   {
469     return viewStyle.getColourText();
470   }
471
472   /**
473    * @param state
474    * @see jalview.api.ViewStyleI#setColourText(boolean)
475    */
476   @Override
477   public void setColourText(boolean state)
478   {
479     viewStyle.setColourText(state);
480   }
481
482   /**
483    * @return
484    * @see jalview.api.ViewStyleI#getWrapAlignment()
485    */
486   @Override
487   public boolean getWrapAlignment()
488   {
489     return viewStyle.getWrapAlignment();
490   }
491
492   /**
493    * @return
494    * @see jalview.api.ViewStyleI#getShowText()
495    */
496   @Override
497   public boolean getShowText()
498   {
499     return viewStyle.getShowText();
500   }
501
502   /**
503    * @return
504    * @see jalview.api.ViewStyleI#getWrappedWidth()
505    */
506   @Override
507   public int getWrappedWidth()
508   {
509     return viewStyle.getWrappedWidth();
510   }
511
512   /**
513    * @param w
514    * @see jalview.api.ViewStyleI#setWrappedWidth(int)
515    */
516   @Override
517   public void setWrappedWidth(int w)
518   {
519     viewStyle.setWrappedWidth(w);
520   }
521
522   /**
523    * @return
524    * @see jalview.api.ViewStyleI#getCharHeight()
525    */
526   @Override
527   public int getCharHeight()
528   {
529     return viewStyle.getCharHeight();
530   }
531
532   /**
533    * @param h
534    * @see jalview.api.ViewStyleI#setCharHeight(int)
535    */
536   @Override
537   public void setCharHeight(int h)
538   {
539     viewStyle.setCharHeight(h);
540   }
541
542   /**
543    * @return
544    * @see jalview.api.ViewStyleI#getCharWidth()
545    */
546   @Override
547   public int getCharWidth()
548   {
549     return viewStyle.getCharWidth();
550   }
551
552   /**
553    * @param w
554    * @see jalview.api.ViewStyleI#setCharWidth(int)
555    */
556   @Override
557   public void setCharWidth(int w)
558   {
559     viewStyle.setCharWidth(w);
560   }
561
562   /**
563    * @return
564    * @see jalview.api.ViewStyleI#getShowBoxes()
565    */
566   @Override
567   public boolean getShowBoxes()
568   {
569     return viewStyle.getShowBoxes();
570   }
571
572   /**
573    * @return
574    * @see jalview.api.ViewStyleI#getShowUnconserved()
575    */
576   @Override
577   public boolean getShowUnconserved()
578   {
579     return viewStyle.getShowUnconserved();
580   }
581
582   /**
583    * @param showunconserved
584    * @see jalview.api.ViewStyleI#setShowUnconserved(boolean)
585    */
586   @Override
587   public void setShowUnconserved(boolean showunconserved)
588   {
589     viewStyle.setShowUnconserved(showunconserved);
590   }
591
592   /**
593    * @param default1
594    * @see jalview.api.ViewStyleI#setSeqNameItalics(boolean)
595    */
596   @Override
597   public void setSeqNameItalics(boolean default1)
598   {
599     viewStyle.setSeqNameItalics(default1);
600   }
601
602   @Override
603   public AlignmentI getAlignment()
604   {
605     return alignment;
606   }
607
608   @Override
609   public char getGapCharacter()
610   {
611     return alignment.getGapCharacter();
612   }
613
614   protected String sequenceSetID;
615
616   /**
617    * probably unused indicator that view is of a dataset rather than an
618    * alignment
619    */
620   protected boolean isDataset = false;
621   
622   public void setDataset(boolean b)
623   {
624     isDataset = b;
625   }
626
627   public boolean isDataset()
628   {
629     return isDataset;
630   }
631
632   private Map<SequenceI, SequenceCollectionI> hiddenRepSequences;
633
634   protected ColumnSelection colSel = new ColumnSelection();
635
636   protected boolean autoCalculateConsensusAndConservation = true;
637
638   public boolean getAutoCalculateConsensusAndConservation()
639   { // BH 2019.07.24
640     return autoCalculateConsensusAndConservation;
641   }
642
643   public void setAutoCalculateConsensusAndConservation(boolean b)
644   {
645     autoCalculateConsensusAndConservation = b;
646   }
647
648   protected boolean autoCalculateStrucConsensus = true;
649
650   public boolean getAutoCalculateStrucConsensus()
651   { // BH 2019.07.24
652     return autoCalculateStrucConsensus;
653   }
654
655   public void setAutoCalculateStrucConsensus(boolean b)
656   {
657     autoCalculateStrucConsensus = b;
658   }
659
660
661   protected boolean ignoreGapsInConsensusCalculation = false;
662
663   protected ResidueShaderI residueShading = new ResidueShader();
664   
665   @Override
666   public void setGlobalColourScheme(ColourSchemeI cs)
667   {
668     // TODO: logic refactored from AlignFrame changeColour -
669     // TODO: autorecalc stuff should be changed to rely on the worker system
670     // check to see if we should implement a changeColour(cs) method rather than
671     // put the logic in here
672     // - means that caller decides if they want to just modify state and defer
673     // calculation till later or to do all calculations in thread.
674     // via changecolour
675
676     /*
677      * only instantiate alignment colouring once, thereafter update it;
678      * this means that any conservation or PID threshold settings
679      * persist when the alignment colour scheme is changed
680      */
681     if (residueShading == null)
682     {
683       residueShading = new ResidueShader(viewStyle);
684     }
685     residueShading.setColourScheme(cs);
686
687     // TODO: do threshold and increment belong in ViewStyle or ResidueShader?
688     // ...problem: groups need these, but do not currently have a ViewStyle
689
690     if (cs != null)
691     {
692       if (getConservationSelected())
693       {
694         residueShading.setConservation(hconservation);
695       }
696       /*
697        * reset conservation flag in case just set to false if
698        * Conservation was null (calculation still in progress)
699        */
700       residueShading.setConservationApplied(getConservationSelected());
701       residueShading.alignmentChanged(alignment, hiddenRepSequences);
702     }
703
704     /*
705      * if 'apply colour to all groups' is selected... do so
706      * (but don't transfer any colour threshold settings to groups)
707      */
708     if (getColourAppliesToAllGroups())
709     {
710       for (SequenceGroup sg : getAlignment().getGroups())
711       {
712         /*
713          * retain any colour thresholds per group while
714          * changing choice of colour scheme (JAL-2386)
715          */
716         sg.setColourScheme(
717                 cs == null ? null : cs.getInstance(this, sg));
718         if (cs != null)
719         {
720           sg.getGroupColourScheme().alignmentChanged(sg,
721                   hiddenRepSequences);
722         }
723       }
724     }
725   }
726
727   @Override
728   public ColourSchemeI getGlobalColourScheme()
729   {
730     return residueShading == null ? null : residueShading.getColourScheme();
731   }
732
733   @Override
734   public ResidueShaderI getResidueShading()
735   {
736     return residueShading;
737   }
738   
739   protected AlignmentAnnotation consensus;
740
741   protected AlignmentAnnotation complementConsensus;
742
743   protected AlignmentAnnotation gapcounts;
744
745   protected AlignmentAnnotation strucConsensus;
746
747   protected AlignmentAnnotation conservation;
748
749   protected AlignmentAnnotation quality;
750
751   protected AlignmentAnnotation[] groupConsensus;
752
753   protected AlignmentAnnotation[] groupConservation;
754
755   /**
756    * results of alignment consensus analysis for visible portion of view
757    */
758   protected ProfilesI hconsensus = null;
759
760   /**
761    * results of cDNA complement consensus visible portion of view
762    */
763   protected Hashtable<String, Object>[] hcomplementConsensus = null;
764
765   /**
766    * results of secondary structure base pair consensus for visible portion of
767    * view
768    */
769   protected Hashtable<String, Object>[] hStrucConsensus = null;
770
771   protected Conservation hconservation = null;
772   
773   @Override
774   public void setConservation(Conservation cons)
775   {
776     hconservation = cons;
777   }
778
779   /**
780    * percentage gaps allowed in a column before all amino acid properties should
781    * be considered unconserved
782    */
783   int ConsPercGaps = 25; // JBPNote : This should be a scalable property!
784
785   @Override
786   public int getConsPercGaps()
787   {
788     return ConsPercGaps;
789   }
790
791   @Override
792   public void setSequenceConsensusHash(ProfilesI hconsensus)
793   {
794     this.hconsensus = hconsensus;
795   }
796
797   @Override
798   public void setComplementConsensusHash(
799           Hashtable<String, Object>[] hconsensus)
800   {
801     this.hcomplementConsensus = hconsensus;
802   }
803
804   @Override
805   public ProfilesI getSequenceConsensusHash()
806   {
807     return hconsensus;
808   }
809
810   @Override
811   public void setHmmProfiles(ProfilesI info)
812   {
813     hmmProfiles = info;
814   }
815
816   @Override
817   public ProfilesI getHmmProfiles()
818   {
819     return hmmProfiles;
820   }
821
822   @Override
823   public Hashtable<String, Object>[] getComplementConsensusHash()
824   {
825     return hcomplementConsensus;
826   }
827
828   @Override
829   public Hashtable<String, Object>[] getRnaStructureConsensusHash()
830   {
831     return hStrucConsensus;
832   }
833
834   @Override
835   public void setRnaStructureConsensusHash(
836           Hashtable<String, Object>[] hStrucConsensus)
837   {
838     this.hStrucConsensus = hStrucConsensus;
839
840   }
841
842   @Override
843   public AlignmentAnnotation getAlignmentQualityAnnot()
844   {
845     return quality;
846   }
847
848   @Override
849   public AlignmentAnnotation getAlignmentConservationAnnotation()
850   {
851     return conservation;
852   }
853
854   @Override
855   public AlignmentAnnotation getAlignmentConsensusAnnotation()
856   {
857     return consensus;
858   }
859
860   @Override
861   public AlignmentAnnotation getAlignmentGapAnnotation()
862   {
863     return gapcounts;
864   }
865
866   @Override
867   public AlignmentAnnotation getComplementConsensusAnnotation()
868   {
869     return complementConsensus;
870   }
871
872   @Override
873   public AlignmentAnnotation getAlignmentStrucConsensusAnnotation()
874   {
875     return strucConsensus;
876   }
877
878   protected AlignCalcManagerI2 calculator = new AlignCalcManager2();
879
880   /**
881    * trigger update of conservation annotation
882    */
883   public void updateConservation(final AlignmentViewPanel ap)
884   {
885     // see note in mantis : issue number 8585
886     if (alignment.isNucleotide()
887             || (conservation == null && quality == null)
888             || !autoCalculateConsensusAndConservation)
889     {
890       return;
891     }
892     if (calculator.getWorkersOfClass(
893             jalview.workers.ConservationThread.class).isEmpty())
894     {
895       calculator.registerWorker(
896               new jalview.workers.ConservationThread(this, ap));
897     }
898   }
899
900   /**
901    * trigger update of consensus annotation
902    */
903   public void updateConsensus(final AlignmentViewPanel ap)
904   {
905     // see note in mantis : issue number 8585
906     if (consensus == null || !autoCalculateConsensusAndConservation)
907     {
908       return;
909     }
910     if (calculator.getWorkersOfClass(ConsensusThread.class).isEmpty())
911     {
912       calculator.registerWorker(new ConsensusThread(this, ap));
913     }
914
915     /*
916      * A separate thread to compute cDNA consensus for a protein alignment
917      * which has mapping to cDNA
918      */
919     final AlignmentI al = this.getAlignment();
920     if (!al.isNucleotide() && al.getCodonFrames() != null
921             && !al.getCodonFrames().isEmpty())
922     {
923       /*
924        * fudge - check first for protein-to-nucleotide mappings
925        * (we don't want to do this for protein-to-protein)
926        */
927       boolean doConsensus = false;
928       for (AlignedCodonFrame mapping : al.getCodonFrames())
929       {
930         // TODO hold mapping type e.g. dna-to-protein in AlignedCodonFrame?
931         MapList[] mapLists = mapping.getdnaToProt();
932         // mapLists can be empty if project load has not finished resolving seqs
933         if (mapLists.length > 0 && mapLists[0].getFromRatio() == 3)
934         {
935           doConsensus = true;
936           break;
937         }
938       }
939       if (doConsensus)
940       {
941         if (calculator.getWorkersOfClass(ComplementConsensusThread.class).isEmpty())
942         {
943           calculator.registerWorker(new ComplementConsensusThread(this, ap));
944         }
945       }
946     }
947   }
948
949   @Override
950   public void initInformationWorker(final AlignmentViewPanel ap)
951   {
952     if (calculator.getWorkersOfClass(InformationThread.class).isEmpty())
953     {
954       calculator.registerWorker(new InformationThread(this, ap));
955     }
956   }
957
958   // --------START Structure Conservation
959   public void updateStrucConsensus(final AlignmentViewPanel ap)
960   {
961     if (autoCalculateStrucConsensus && strucConsensus == null
962             && alignment.isNucleotide() && alignment.hasRNAStructure())
963     {
964       // secondary structure has been added - so init the consensus line
965       initRNAStructure();
966     }
967
968     // see note in mantis : issue number 8585
969     if (strucConsensus == null || !autoCalculateStrucConsensus)
970     {
971       return;
972     }
973     if (calculator.getWorkersOfClass(StrucConsensusThread.class).isEmpty())
974     {
975       calculator.registerWorker(new StrucConsensusThread(this, ap));
976     }
977   }
978
979   public boolean isCalcInProgress()
980   {
981     return calculator.isWorking();
982   }
983
984   @Override
985   public boolean isCalculationInProgress(
986           AlignmentAnnotation alignmentAnnotation)
987   {
988     if (!alignmentAnnotation.autoCalculated)
989     {
990       return false;
991     }
992     if (calculator.isWorkingWithAnnotation(alignmentAnnotation))
993     {
994       // System.err.println("grey out ("+alignmentAnnotation.label+")");
995       return true;
996     }
997     return false;
998   }
999
1000   public void setAlignment(AlignmentI align)
1001   {
1002     this.alignment = align;
1003   }
1004
1005   /**
1006    * Clean up references when this viewport is closed
1007    */
1008   @Override
1009   public void dispose()
1010   {
1011     /*
1012      * defensively null out references to large objects in case
1013      * this object is not garbage collected (as if!)
1014      */
1015     consensus = null;
1016     complementConsensus = null;
1017     strucConsensus = null;
1018     conservation = null;
1019     quality = null;
1020     consensusProfiles = null;
1021     groupConsensus = null;
1022     groupConservation = null;
1023     hconsensus = null;
1024     hconservation = null;
1025     hcomplementConsensus = null;
1026     gapcounts = null;
1027     calculator.shutdown();
1028     calculator = null;
1029     residueShading = null; // may hold a reference to Consensus
1030     changeSupport = null;
1031     ranges = null;
1032     currentTree = null;
1033     selectionGroup = null;
1034     setAlignment(null);
1035   }
1036
1037   @Override
1038   public boolean isClosed()
1039   {
1040     // TODO: check that this isClosed is only true after panel is closed, not
1041     // before it is fully constructed.
1042     return alignment == null;
1043   }
1044
1045   @Override
1046   public AlignCalcManagerI2 getCalcManager()
1047   {
1048     return calculator;
1049   }
1050
1051   /**
1052    * should conservation rows be shown for groups
1053    */
1054   protected boolean showGroupConservation = false;
1055
1056   /**
1057    * should consensus rows be shown for groups
1058    */
1059   protected boolean showGroupConsensus = false;
1060
1061   /**
1062    * should consensus profile be rendered by default
1063    */
1064   protected boolean showSequenceLogo = false;
1065
1066   /**
1067    * should consensus profile be rendered normalised to row height
1068    */
1069   protected boolean normaliseSequenceLogo = false;
1070
1071   /**
1072    * should consensus histograms be rendered by default
1073    */
1074   protected boolean showConsensusHistogram = true;
1075
1076   /**
1077    * should hmm profile be rendered by default
1078    */
1079   protected boolean hmmShowSequenceLogo = false;
1080
1081   /**
1082    * should hmm profile be rendered normalised to row height
1083    */
1084   protected boolean hmmNormaliseSequenceLogo = false;
1085
1086   /**
1087    * should information histograms be rendered by default
1088    */
1089   protected boolean hmmShowHistogram = true;
1090
1091   /**
1092    * @return the showConsensusProfile
1093    */
1094   @Override
1095   public boolean isShowSequenceLogo()
1096   {
1097     return showSequenceLogo;
1098   }
1099
1100   /**
1101    * @return the showInformationProfile
1102    */
1103   @Override
1104   public boolean isShowHMMSequenceLogo()
1105   {
1106     return hmmShowSequenceLogo;
1107   }
1108
1109   /**
1110    * @param showSequenceLogo
1111    *          the new value
1112    */
1113   public void setShowSequenceLogo(boolean showSequenceLogo)
1114   {
1115     if (showSequenceLogo != this.showSequenceLogo)
1116     {
1117       // TODO: decouple settings setting from calculation when refactoring
1118       // annotation update method from alignframe to viewport
1119       this.showSequenceLogo = showSequenceLogo;
1120       for (AlignCalcWorkerI worker : calculator.getWorkers())
1121       {
1122         if (worker.getClass().equals(ConsensusThread.class) ||
1123                 worker.getClass().equals(ComplementConsensusThread.class) ||
1124                 worker.getClass().equals(StrucConsensusThread.class))
1125         {
1126           worker.updateAnnotation();
1127         }
1128       }
1129     }
1130     this.showSequenceLogo = showSequenceLogo;
1131   }
1132
1133   public void setShowHMMSequenceLogo(boolean showHMMSequenceLogo)
1134   {
1135     if (showHMMSequenceLogo != this.hmmShowSequenceLogo)
1136     {
1137       this.hmmShowSequenceLogo = showHMMSequenceLogo;
1138       // TODO: updateAnnotation if description (tooltip) will show
1139       // profile in place of information content?
1140       // calculator.updateAnnotationFor(InformationThread.class);
1141     }
1142     this.hmmShowSequenceLogo = showHMMSequenceLogo;
1143   }
1144
1145   /**
1146    * @param showConsensusHistogram
1147    *          the showConsensusHistogram to set
1148    */
1149   public void setShowConsensusHistogram(boolean showConsensusHistogram)
1150   {
1151     this.showConsensusHistogram = showConsensusHistogram;
1152   }
1153
1154   /**
1155    * @param showInformationHistogram
1156    */
1157   public void setShowInformationHistogram(boolean showInformationHistogram)
1158   {
1159     this.hmmShowHistogram = showInformationHistogram;
1160   }
1161
1162   /**
1163    * @return the showGroupConservation
1164    */
1165   public boolean isShowGroupConservation()
1166   {
1167     return showGroupConservation;
1168   }
1169
1170   /**
1171    * @param showGroupConservation
1172    *          the showGroupConservation to set
1173    */
1174   public void setShowGroupConservation(boolean showGroupConservation)
1175   {
1176     this.showGroupConservation = showGroupConservation;
1177   }
1178
1179   /**
1180    * @return the showGroupConsensus
1181    */
1182   public boolean isShowGroupConsensus()
1183   {
1184     return showGroupConsensus;
1185   }
1186
1187   /**
1188    * @param showGroupConsensus
1189    *          the showGroupConsensus to set
1190    */
1191   public void setShowGroupConsensus(boolean showGroupConsensus)
1192   {
1193     this.showGroupConsensus = showGroupConsensus;
1194   }
1195
1196   /**
1197    * 
1198    * @return flag to indicate if the consensus histogram should be rendered by
1199    *         default
1200    */
1201   @Override
1202   public boolean isShowConsensusHistogram()
1203   {
1204     return this.showConsensusHistogram;
1205   }
1206
1207   /**
1208    * 
1209    * @return flag to indicate if the information content histogram should be
1210    *         rendered by default
1211    */
1212   @Override
1213   public boolean isShowInformationHistogram()
1214   {
1215     return this.hmmShowHistogram;
1216   }
1217
1218   /**
1219    * when set, updateAlignment will always ensure sequences are of equal length
1220    */
1221   private boolean padGaps = false;
1222
1223   /**
1224    * when set, alignment should be reordered according to a newly opened tree
1225    */
1226   public boolean sortByTree = false;
1227
1228   /**
1229    * 
1230    * 
1231    * @return null or the currently selected sequence region
1232    */
1233   @Override
1234   public SequenceGroup getSelectionGroup()
1235   {
1236     return selectionGroup;
1237   }
1238
1239   /**
1240    * Set the selection group for this window. Also sets the current alignment as
1241    * the context for the group, if it does not already have one.
1242    * 
1243    * @param sg
1244    *          - group holding references to sequences in this alignment view
1245    * 
1246    */
1247   @Override
1248   public void setSelectionGroup(SequenceGroup sg)
1249   {
1250     selectionGroup = sg;
1251     if (sg != null && sg.getContext() == null)
1252     {
1253       sg.setContext(alignment);
1254     }
1255   }
1256
1257   public void setHiddenColumns(HiddenColumns hidden)
1258   {
1259     this.alignment.setHiddenColumns(hidden);
1260   }
1261
1262   @Override
1263   public ColumnSelection getColumnSelection()
1264   {
1265     return colSel;
1266   }
1267
1268   @Override
1269   public void setColumnSelection(ColumnSelection colSel)
1270   {
1271     this.colSel = colSel;
1272     if (colSel != null)
1273     {
1274       updateHiddenColumns();
1275     }
1276     isColSelChanged(true);
1277   }
1278
1279   /**
1280    * 
1281    * @return
1282    */
1283   @Override
1284   public Map<SequenceI, SequenceCollectionI> getHiddenRepSequences()
1285   {
1286     return hiddenRepSequences;
1287   }
1288
1289   @Override
1290   public void setHiddenRepSequences(
1291           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
1292   {
1293     this.hiddenRepSequences = hiddenRepSequences;
1294   }
1295
1296   @Override
1297   public boolean hasSelectedColumns()
1298   {
1299     ColumnSelection columnSelection = getColumnSelection();
1300     return columnSelection != null && columnSelection.hasSelectedColumns();
1301   }
1302
1303   @Override
1304   public boolean hasHiddenColumns()
1305   {
1306     return alignment.getHiddenColumns() != null
1307             && alignment.getHiddenColumns().hasHiddenColumns();
1308   }
1309
1310   public void updateHiddenColumns()
1311   {
1312     // this method doesn't really do anything now. But - it could, since a
1313     // column Selection could be in the process of modification
1314     // hasHiddenColumns = colSel.hasHiddenColumns();
1315   }
1316
1317   @Override
1318   public boolean hasHiddenRows()
1319   {
1320     return alignment.getHiddenSequences().getSize() > 0;
1321   }
1322
1323   protected SequenceGroup selectionGroup;
1324
1325   public void setSequenceSetId(String newid)
1326   {
1327     if (sequenceSetID != null)
1328     {
1329       System.err.println(
1330               "Warning - overwriting a sequenceSetId for a viewport!");
1331     }
1332     sequenceSetID = new String(newid);
1333   }
1334
1335   @Override
1336   public String getSequenceSetId()
1337   {
1338     if (sequenceSetID == null)
1339     {
1340       sequenceSetID = alignment.hashCode() + "";
1341     }
1342
1343     return sequenceSetID;
1344   }
1345
1346   /**
1347    * unique viewId for synchronizing state (e.g. with stored Jalview Project)
1348    * 
1349    */
1350   protected String viewId = null;
1351
1352   @Override
1353   public String getViewId()
1354   {
1355     if (viewId == null)
1356     {
1357       viewId = this.getSequenceSetId() + "." + this.hashCode() + "";
1358     }
1359     return viewId;
1360   }
1361
1362   public void setIgnoreGapsConsensus(boolean b, AlignmentViewPanel ap)
1363   {
1364     ignoreGapsInConsensusCalculation = b;
1365     if (ap != null)
1366     {
1367       updateConsensus(ap);
1368       if (residueShading != null)
1369       {
1370         residueShading.setThreshold(residueShading.getThreshold(),
1371                 ignoreGapsInConsensusCalculation);
1372       }
1373     }
1374   }
1375
1376   public void setIgnoreBelowBackground(boolean b, AlignmentViewPanel ap)
1377   {
1378     ignoreBelowBackGroundFrequencyCalculation = b;
1379   }
1380
1381   public void setInfoLetterHeight(boolean b, AlignmentViewPanel ap)
1382   {
1383     infoLetterHeight = b;
1384   }
1385
1386   private long sgrouphash = -1, colselhash = -1;
1387
1388   /**
1389    * checks current SelectionGroup against record of last hash value, and
1390    * updates record.
1391    * 
1392    * @param b
1393    *          update the record of last hash value
1394    * 
1395    * @return true if SelectionGroup changed since last call (when b is true)
1396    */
1397   public boolean isSelectionGroupChanged(boolean b)
1398   {
1399     int hc = (selectionGroup == null || selectionGroup.getSize() == 0) ? -1
1400             : selectionGroup.hashCode();
1401     if (hc != -1 && hc != sgrouphash)
1402     {
1403       if (b)
1404       {
1405         sgrouphash = hc;
1406       }
1407       return true;
1408     }
1409     return false;
1410   }
1411
1412   /**
1413    * checks current colsel against record of last hash value, and optionally
1414    * updates record.
1415    * 
1416    * @param updateHash
1417    *          update the record of last hash value
1418    * @return true if colsel changed since last call (when b is true)
1419    */
1420   public boolean isColSelChanged(boolean updateHash)
1421   {
1422     int hc = (colSel == null || colSel.isEmpty()) ? -1 : colSel.hashCode();
1423     if (hc != -1 && hc != colselhash)
1424     {
1425       if (updateHash)
1426       {
1427         colselhash = hc;
1428       }
1429       return true;
1430     }
1431     notifySequence();
1432     return false;
1433   }
1434
1435   @Override
1436   public boolean isIgnoreGapsConsensus()
1437   {
1438     return ignoreGapsInConsensusCalculation;
1439   }
1440
1441   @Override
1442   public boolean isIgnoreBelowBackground()
1443   {
1444     return ignoreBelowBackGroundFrequencyCalculation;
1445   }
1446
1447   @Override
1448   public boolean isInfoLetterHeight()
1449   {
1450     return infoLetterHeight;
1451   }
1452
1453   // property change stuff
1454   // JBPNote Prolly only need this in the applet version.
1455   private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
1456           this);
1457
1458   protected boolean showConservation = true;
1459
1460   protected boolean showQuality = true;
1461
1462   protected boolean showConsensus = true;
1463
1464   protected boolean showOccupancy = true;
1465
1466   private Map<SequenceI, Color> sequenceColours = new HashMap<>();
1467
1468   protected SequenceAnnotationOrder sortAnnotationsBy = null;
1469
1470   protected boolean showAutocalculatedAbove;
1471
1472   /**
1473    * when set, view will scroll to show the highlighted position
1474    */
1475   private boolean followHighlight = true;
1476
1477   /**
1478    * Property change listener for changes in alignment
1479    * 
1480    * @param listener
1481    *          DOCUMENT ME!
1482    */
1483   public void addPropertyChangeListener(
1484           java.beans.PropertyChangeListener listener)
1485   {
1486     changeSupport.addPropertyChangeListener(listener);
1487   }
1488
1489   /**
1490    * DOCUMENT ME!
1491    * 
1492    * @param listener
1493    *          DOCUMENT ME!
1494    */
1495   public void removePropertyChangeListener(
1496           java.beans.PropertyChangeListener listener)
1497   {
1498     if (changeSupport != null)
1499     {
1500       changeSupport.removePropertyChangeListener(listener);
1501     }
1502   }
1503
1504   // common hide/show column stuff
1505
1506   public void hideSelectedColumns()
1507   {
1508     if (colSel.isEmpty())
1509     {
1510       return;
1511     }
1512
1513     colSel.hideSelectedColumns(alignment);
1514     setSelectionGroup(null);
1515     isColSelChanged(true);
1516   }
1517
1518   public void hideColumns(int start, int end)
1519   {
1520     if (start == end)
1521     {
1522       colSel.hideSelectedColumns(start, alignment.getHiddenColumns());
1523     }
1524     else
1525     {
1526       alignment.getHiddenColumns().hideColumns(start, end);
1527     }
1528     isColSelChanged(true);
1529   }
1530
1531   public void showColumn(int col)
1532   {
1533     alignment.getHiddenColumns().revealHiddenColumns(col, colSel);
1534     isColSelChanged(true);
1535   }
1536
1537   public void showAllHiddenColumns()
1538   {
1539     alignment.getHiddenColumns().revealAllHiddenColumns(colSel);
1540     isColSelChanged(true);
1541   }
1542
1543   // common hide/show seq stuff
1544   public void showAllHiddenSeqs()
1545   {
1546     int startSeq = ranges.getStartSeq();
1547     int endSeq = ranges.getEndSeq();
1548
1549     if (alignment.getHiddenSequences().getSize() > 0)
1550     {
1551       if (selectionGroup == null)
1552       {
1553         selectionGroup = new SequenceGroup();
1554         selectionGroup.setEndRes(alignment.getWidth() - 1);
1555       }
1556       List<SequenceI> tmp = alignment.getHiddenSequences()
1557               .showAll(hiddenRepSequences);
1558       for (SequenceI seq : tmp)
1559       {
1560         selectionGroup.addSequence(seq, false);
1561         setSequenceAnnotationsVisible(seq, true);
1562       }
1563
1564       hiddenRepSequences = null;
1565
1566       ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
1567
1568       // used to set hasHiddenRows/hiddenRepSequences here, after the property
1569       // changed event
1570       notifySequence();
1571       sendSelection();
1572     }
1573   }
1574
1575
1576   public void showSequence(int index)
1577   {
1578     int startSeq = ranges.getStartSeq();
1579     int endSeq = ranges.getEndSeq();
1580
1581     List<SequenceI> tmp = alignment.getHiddenSequences().showSequence(index,
1582             hiddenRepSequences);
1583     if (tmp.size() > 0)
1584     {
1585       if (selectionGroup == null)
1586       {
1587         selectionGroup = new SequenceGroup();
1588         selectionGroup.setEndRes(alignment.getWidth() - 1);
1589       }
1590
1591       for (SequenceI seq : tmp)
1592       {
1593         selectionGroup.addSequence(seq, false);
1594         setSequenceAnnotationsVisible(seq, true);
1595       }
1596
1597       ranges.setStartEndSeq(startSeq, endSeq + tmp.size());
1598       notifyAlignment();
1599       sendSelection();
1600     }
1601   }
1602
1603   public void hideAllSelectedSeqs()
1604   {
1605     if (selectionGroup == null || selectionGroup.getSize() < 1)
1606     {
1607       return;
1608     }
1609
1610     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
1611
1612     hideSequence(seqs);
1613
1614     setSelectionGroup(null);
1615   }
1616
1617   public void hideSequence(SequenceI[] seq)
1618   {
1619     /*
1620      * cache offset to first visible sequence
1621      */
1622     int startSeq = ranges.getStartSeq();
1623
1624     if (seq != null)
1625     {
1626       for (int i = 0; i < seq.length; i++)
1627       {
1628         alignment.getHiddenSequences().hideSequence(seq[i]);
1629         setSequenceAnnotationsVisible(seq[i], false);
1630       }
1631       ranges.setStartSeq(startSeq);
1632       notifyAlignment();
1633     }
1634   }
1635
1636   /**
1637    * Hides the specified sequence, or the sequences it represents
1638    * 
1639    * @param sequence
1640    *          the sequence to hide, or keep as representative
1641    * @param representGroup
1642    *          if true, hide the current selection group except for the
1643    *          representative sequence
1644    */
1645   public void hideSequences(SequenceI sequence, boolean representGroup)
1646   {
1647     if (selectionGroup == null || selectionGroup.getSize() < 1)
1648     {
1649       hideSequence(new SequenceI[] { sequence });
1650       return;
1651     }
1652
1653     if (representGroup)
1654     {
1655       hideRepSequences(sequence, selectionGroup);
1656       setSelectionGroup(null);
1657       return;
1658     }
1659
1660     int gsize = selectionGroup.getSize();
1661     SequenceI[] hseqs = selectionGroup.getSequences()
1662             .toArray(new SequenceI[gsize]);
1663
1664     hideSequence(hseqs);
1665     setSelectionGroup(null);
1666     sendSelection();
1667   }
1668
1669   /**
1670    * Set visibility for any annotations for the given sequence.
1671    * 
1672    * @param sequenceI
1673    */
1674   protected void setSequenceAnnotationsVisible(SequenceI sequenceI,
1675           boolean visible)
1676   {
1677     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
1678     if (anns != null)
1679     {
1680       for (AlignmentAnnotation ann : anns)
1681       {
1682         if (ann.sequenceRef == sequenceI)
1683         {
1684           ann.visible = visible;
1685         }
1686       }
1687     }
1688   }
1689
1690   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
1691   {
1692     int sSize = sg.getSize();
1693     if (sSize < 2)
1694     {
1695       return;
1696     }
1697
1698     if (hiddenRepSequences == null)
1699     {
1700       hiddenRepSequences = new Hashtable<>();
1701     }
1702
1703     hiddenRepSequences.put(repSequence, sg);
1704
1705     // Hide all sequences except the repSequence
1706     SequenceI[] seqs = new SequenceI[sSize - 1];
1707     int index = 0;
1708     for (int i = 0; i < sSize; i++)
1709     {
1710       if (sg.getSequenceAt(i) != repSequence)
1711       {
1712         if (index == sSize - 1)
1713         {
1714           return;
1715         }
1716
1717         seqs[index++] = sg.getSequenceAt(i);
1718       }
1719     }
1720     sg.setSeqrep(repSequence); // note: not done in 2.7applet
1721     sg.setHidereps(true); // note: not done in 2.7applet
1722     hideSequence(seqs);
1723
1724   }
1725
1726   /**
1727    * 
1728    * @return null or the current reference sequence
1729    */
1730   public SequenceI getReferenceSeq()
1731   {
1732     return alignment.getSeqrep();
1733   }
1734
1735   /**
1736    * @param seq
1737    * @return true iff seq is the reference for the alignment
1738    */
1739   public boolean isReferenceSeq(SequenceI seq)
1740   {
1741     return alignment.getSeqrep() == seq;
1742   }
1743
1744   /**
1745    * 
1746    * @param seq
1747    * @return true if there are sequences represented by this sequence that are
1748    *         currently hidden
1749    */
1750   public boolean isHiddenRepSequence(SequenceI seq)
1751   {
1752     return (hiddenRepSequences != null
1753             && hiddenRepSequences.containsKey(seq));
1754   }
1755
1756   /**
1757    * 
1758    * @param seq
1759    * @return null or a sequence group containing the sequences that seq
1760    *         represents
1761    */
1762   public SequenceGroup getRepresentedSequences(SequenceI seq)
1763   {
1764     return (SequenceGroup) (hiddenRepSequences == null ? null
1765             : hiddenRepSequences.get(seq));
1766   }
1767
1768   @Override
1769   public int adjustForHiddenSeqs(int alignmentIndex)
1770   {
1771     return alignment.getHiddenSequences()
1772             .adjustForHiddenSeqs(alignmentIndex);
1773   }
1774
1775   @Override
1776   public void invertColumnSelection()
1777   {
1778     colSel.invertColumnSelection(0, alignment.getWidth(), alignment);
1779     isColSelChanged(true);
1780   }
1781
1782   @Override
1783   public SequenceI[] getSelectionAsNewSequence()
1784   {
1785     SequenceI[] sequences;
1786     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1787     // this was the only caller in the applet for this method
1788     // JBPNote: in applet, this method returned references to the alignment
1789     // sequences, and it did not honour the presence/absence of annotation
1790     // attached to the alignment (probably!)
1791     if (selectionGroup == null || selectionGroup.getSize() == 0)
1792     {
1793       sequences = alignment.getSequencesArray();
1794       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1795       for (int i = 0; i < sequences.length; i++)
1796       {
1797         // construct new sequence with subset of visible annotation
1798         sequences[i] = new Sequence(sequences[i], annots);
1799       }
1800     }
1801     else
1802     {
1803       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1804     }
1805
1806     return sequences;
1807   }
1808
1809   @Override
1810   public SequenceI[] getSequenceSelection()
1811   {
1812     SequenceI[] sequences = null;
1813     if (selectionGroup != null)
1814     {
1815       sequences = selectionGroup.getSequencesInOrder(alignment);
1816     }
1817     if (sequences == null)
1818     {
1819       sequences = alignment.getSequencesArray();
1820     }
1821     return sequences;
1822   }
1823
1824   @Override
1825   public jalview.datamodel.AlignmentView getAlignmentView(
1826           boolean selectedOnly)
1827   {
1828     return getAlignmentView(selectedOnly, false);
1829   }
1830
1831   @Override
1832   public jalview.datamodel.AlignmentView getAlignmentView(
1833           boolean selectedOnly, boolean markGroups)
1834   {
1835     return new AlignmentView(alignment, alignment.getHiddenColumns(),
1836             selectionGroup,
1837             alignment.getHiddenColumns() != null
1838                     && alignment.getHiddenColumns().hasHiddenColumns(),
1839             selectedOnly, markGroups);
1840   }
1841
1842   @Override
1843   public String[] getViewAsString(boolean selectedRegionOnly)
1844   {
1845     return getViewAsString(selectedRegionOnly, true);
1846   }
1847
1848   @Override
1849   public String[] getViewAsString(boolean selectedRegionOnly,
1850           boolean exportHiddenSeqs)
1851   {
1852     String[] selection = null;
1853     SequenceI[] seqs = null;
1854     int i, iSize;
1855     int start = 0, end = 0;
1856     if (selectedRegionOnly && selectionGroup != null)
1857     {
1858       iSize = selectionGroup.getSize();
1859       seqs = selectionGroup.getSequencesInOrder(alignment);
1860       start = selectionGroup.getStartRes();
1861       end = selectionGroup.getEndRes() + 1;
1862     }
1863     else
1864     {
1865       if (hasHiddenRows() && exportHiddenSeqs)
1866       {
1867         AlignmentI fullAlignment = alignment.getHiddenSequences()
1868                 .getFullAlignment();
1869         iSize = fullAlignment.getHeight();
1870         seqs = fullAlignment.getSequencesArray();
1871         end = fullAlignment.getWidth();
1872       }
1873       else
1874       {
1875         iSize = alignment.getHeight();
1876         seqs = alignment.getSequencesArray();
1877         end = alignment.getWidth();
1878       }
1879     }
1880
1881     selection = new String[iSize];
1882     if (alignment.getHiddenColumns() != null
1883             && alignment.getHiddenColumns().hasHiddenColumns())
1884     {
1885       for (i = 0; i < iSize; i++)
1886       {
1887         Iterator<int[]> blocks = alignment.getHiddenColumns()
1888                 .getVisContigsIterator(start, end + 1, false);
1889         selection[i] = seqs[i].getSequenceStringFromIterator(blocks);
1890       }
1891     }
1892     else
1893     {
1894       for (i = 0; i < iSize; i++)
1895       {
1896         selection[i] = seqs[i].getSequenceAsString(start, end);
1897       }
1898
1899     }
1900     return selection;
1901   }
1902
1903   @Override
1904   public List<int[]> getVisibleRegionBoundaries(int min, int max)
1905   {
1906     ArrayList<int[]> regions = new ArrayList<>();
1907     int start = min;
1908     int end = max;
1909
1910     do
1911     {
1912       HiddenColumns hidden = alignment.getHiddenColumns();
1913       if (hidden != null && hidden.hasHiddenColumns())
1914       {
1915         if (start == 0)
1916         {
1917           start = hidden.visibleToAbsoluteColumn(start);
1918         }
1919
1920         end = hidden.getNextHiddenBoundary(false, start);
1921         if (start == end)
1922         {
1923           end = max;
1924         }
1925         if (end > max)
1926         {
1927           end = max;
1928         }
1929       }
1930
1931       regions.add(new int[] { start, end });
1932
1933       if (hidden != null && hidden.hasHiddenColumns())
1934       {
1935         start = hidden.visibleToAbsoluteColumn(end);
1936         start = hidden.getNextHiddenBoundary(true, start) + 1;
1937       }
1938     } while (end < max);
1939
1940     // int[][] startEnd = new int[regions.size()][2];
1941
1942     return regions;
1943   }
1944
1945   @Override
1946   public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(
1947           boolean selectedOnly)
1948   {
1949     ArrayList<AlignmentAnnotation> ala = new ArrayList<>();
1950     AlignmentAnnotation[] aa;
1951     if ((aa = alignment.getAlignmentAnnotation()) != null)
1952     {
1953       for (AlignmentAnnotation annot : aa)
1954       {
1955         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
1956         if (selectedOnly && selectionGroup != null)
1957         {
1958           clone.makeVisibleAnnotation(
1959                   selectionGroup.getStartRes(), selectionGroup.getEndRes(),
1960                   alignment.getHiddenColumns());
1961         }
1962         else
1963         {
1964           clone.makeVisibleAnnotation(alignment.getHiddenColumns());
1965         }
1966         ala.add(clone);
1967       }
1968     }
1969     return ala;
1970   }
1971
1972   @Override
1973   public boolean isPadGaps()
1974   {
1975     return padGaps;
1976   }
1977
1978   @Override
1979   public void setPadGaps(boolean padGaps)
1980   {
1981     this.padGaps = padGaps;
1982   }
1983
1984   /**
1985    * apply any post-edit constraints and trigger any calculations needed after
1986    * an edit has been performed on the alignment
1987    * 
1988    * @param ap
1989    */
1990   @Override
1991   public void alignmentChanged(AlignmentViewPanel ap)
1992   {
1993     if (isPadGaps())
1994     {
1995       alignment.padGaps();
1996     }
1997     if (autoCalculateConsensusAndConservation)
1998     {
1999       updateConsensus(ap);
2000     }
2001     if (hconsensus != null && autoCalculateConsensusAndConservation)
2002     {
2003       updateConservation(ap);
2004     }
2005     if (autoCalculateStrucConsensus)
2006     {
2007       updateStrucConsensus(ap);
2008     }
2009
2010     // Reset endRes of groups if beyond alignment width
2011     int alWidth = alignment.getWidth();
2012     List<SequenceGroup> groups = alignment.getGroups();
2013     if (groups != null)
2014     {
2015       for (SequenceGroup sg : groups)
2016       {
2017         if (sg.getEndRes() > alWidth)
2018         {
2019           sg.setEndRes(alWidth - 1);
2020         }
2021       }
2022     }
2023
2024     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
2025     {
2026       selectionGroup.setEndRes(alWidth - 1);
2027     }
2028
2029     updateAllColourSchemes();
2030     calculator.restartWorkers();
2031   }
2032
2033   /**
2034    * reset scope and do calculations for all applied colourschemes on alignment
2035    */
2036   void updateAllColourSchemes()
2037   {
2038     ResidueShaderI rs = residueShading;
2039     if (rs != null)
2040     {
2041       rs.alignmentChanged(alignment, hiddenRepSequences);
2042
2043       rs.setConsensus(hconsensus);
2044       if (rs.conservationApplied())
2045       {
2046         rs.setConservation(Conservation.calculateConservation("All",
2047                 alignment.getSequences(), 0, alignment.getWidth(), false,
2048                 getConsPercGaps(), false));
2049       }
2050     }
2051
2052     for (SequenceGroup sg : alignment.getGroups())
2053     {
2054       if (sg.cs != null)
2055       {
2056         sg.cs.alignmentChanged(sg, hiddenRepSequences);
2057       }
2058       sg.recalcConservation();
2059     }
2060   }
2061
2062   protected void initAutoAnnotation()
2063   {
2064     // TODO: add menu option action that nulls or creates consensus object
2065     // depending on if the user wants to see the annotation or not in a
2066     // specific alignment
2067
2068     if (hconsensus == null && !isDataset)
2069     {
2070       if (!alignment.isNucleotide())
2071       {
2072         initConservation();
2073         initQuality();
2074       }
2075       else
2076       {
2077         initRNAStructure();
2078       }
2079       consensus = new AlignmentAnnotation("Consensus",
2080               MessageManager.getString("label.consensus_descr"),
2081               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
2082       initConsensus(consensus);
2083
2084       initGapCounts();
2085
2086       initComplementConsensus();
2087     }
2088   }
2089
2090   /**
2091    * If this is a protein alignment and there are mappings to cDNA, adds the
2092    * cDNA consensus annotation and returns true, else returns false.
2093    */
2094   public boolean initComplementConsensus()
2095   {
2096     if (!alignment.isNucleotide())
2097     {
2098       final List<AlignedCodonFrame> codonMappings = alignment
2099               .getCodonFrames();
2100       if (codonMappings != null && !codonMappings.isEmpty())
2101       {
2102         boolean doConsensus = false;
2103         for (AlignedCodonFrame mapping : codonMappings)
2104         {
2105           // TODO hold mapping type e.g. dna-to-protein in AlignedCodonFrame?
2106           MapList[] mapLists = mapping.getdnaToProt();
2107           // mapLists can be empty if project load has not finished resolving
2108           // seqs
2109           if (mapLists.length > 0 && mapLists[0].getFromRatio() == 3)
2110           {
2111             doConsensus = true;
2112             break;
2113           }
2114         }
2115         if (doConsensus)
2116         {
2117           complementConsensus = new AlignmentAnnotation("cDNA Consensus",
2118                   MessageManager
2119                           .getString("label.complement_consensus_descr"),
2120                   new Annotation[1], 0f, 100f,
2121                   AlignmentAnnotation.BAR_GRAPH);
2122           initConsensus(complementConsensus);
2123           return true;
2124         }
2125       }
2126     }
2127     return false;
2128   }
2129
2130   private void initConsensus(AlignmentAnnotation aa)
2131   {
2132     aa.hasText = true;
2133     aa.autoCalculated = true;
2134
2135     if (showConsensus)
2136     {
2137       alignment.addAnnotation(aa);
2138     }
2139   }
2140
2141   // these should be extracted from the view model - style and settings for
2142   // derived annotation
2143   private void initGapCounts()
2144   {
2145     if (showOccupancy)
2146     {
2147       gapcounts = new AlignmentAnnotation("Occupancy",
2148               MessageManager.getString("label.occupancy_descr"),
2149               new Annotation[1], 0f, alignment.getHeight(),
2150               AlignmentAnnotation.BAR_GRAPH);
2151       gapcounts.hasText = true;
2152       gapcounts.autoCalculated = true;
2153       gapcounts.scaleColLabel = true;
2154       gapcounts.graph = AlignmentAnnotation.BAR_GRAPH;
2155
2156       alignment.addAnnotation(gapcounts);
2157     }
2158   }
2159
2160   private void initConservation()
2161   {
2162     if (showConservation)
2163     {
2164       if (conservation == null)
2165       {
2166         conservation = new AlignmentAnnotation("Conservation",
2167                 MessageManager.formatMessage("label.conservation_descr",
2168                         getConsPercGaps()),
2169                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
2170         conservation.hasText = true;
2171         conservation.autoCalculated = true;
2172         alignment.addAnnotation(conservation);
2173       }
2174     }
2175   }
2176
2177   private void initQuality()
2178   {
2179     if (showQuality)
2180     {
2181       if (quality == null)
2182       {
2183         quality = new AlignmentAnnotation("Quality",
2184                 MessageManager.getString("label.quality_descr"),
2185                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
2186         quality.hasText = true;
2187         quality.autoCalculated = true;
2188         alignment.addAnnotation(quality);
2189       }
2190     }
2191   }
2192
2193   private void initRNAStructure()
2194   {
2195     if (alignment.hasRNAStructure() && strucConsensus == null)
2196     {
2197       strucConsensus = new AlignmentAnnotation("StrucConsensus",
2198               MessageManager.getString("label.strucconsensus_descr"),
2199               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
2200       strucConsensus.hasText = true;
2201       strucConsensus.autoCalculated = true;
2202
2203       if (showConsensus)
2204       {
2205         alignment.addAnnotation(strucConsensus);
2206       }
2207     }
2208   }
2209
2210   /*
2211    * (non-Javadoc)
2212    * 
2213    * @see jalview.api.AlignViewportI#calcPanelHeight()
2214    */
2215   @Override
2216   public int calcPanelHeight()
2217   {
2218     // setHeight of panels
2219     AlignmentAnnotation[] anns = getAlignment().getAlignmentAnnotation();
2220     int height = 0;
2221     int charHeight = getCharHeight();
2222     if (anns != null)
2223     {
2224       BitSet graphgrp = new BitSet();
2225       for (AlignmentAnnotation aa : anns)
2226       {
2227         if (aa == null)
2228         {
2229           System.err.println("Null annotation row: ignoring.");
2230           continue;
2231         }
2232         if (!aa.visible)
2233         {
2234           continue;
2235         }
2236         if (aa.graphGroup > -1)
2237         {
2238           if (graphgrp.get(aa.graphGroup))
2239           {
2240             continue;
2241           }
2242           else
2243           {
2244             graphgrp.set(aa.graphGroup);
2245           }
2246         }
2247         aa.height = 0;
2248
2249         if (aa.hasText)
2250         {
2251           aa.height += charHeight;
2252         }
2253
2254         if (aa.hasIcons)
2255         {
2256           aa.height += 16;
2257         }
2258
2259         if (aa.graph > 0)
2260         {
2261           aa.height += aa.graphHeight;
2262         }
2263
2264         if (aa.height == 0)
2265         {
2266           aa.height = 20;
2267         }
2268
2269         height += aa.height;
2270       }
2271     }
2272     if (height == 0)
2273     {
2274       // set minimum
2275       height = 20;
2276     }
2277     return height;
2278   }
2279
2280   @Override
2281   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
2282           boolean preserveNewGroupSettings)
2283   {
2284     boolean updateCalcs = false;
2285     boolean conv = isShowGroupConservation();
2286     boolean cons = isShowGroupConsensus();
2287     boolean showprf = isShowSequenceLogo();
2288     boolean showConsHist = isShowConsensusHistogram();
2289     boolean normLogo = isNormaliseSequenceLogo();
2290     boolean showHMMPrf = isShowHMMSequenceLogo();
2291     boolean showInfoHist = isShowInformationHistogram();
2292     boolean normHMMLogo = isNormaliseHMMSequenceLogo();
2293
2294     /**
2295      * TODO reorder the annotation rows according to group/sequence ordering on
2296      * alignment
2297      */
2298     // boolean sortg = true;
2299
2300     // remove old automatic annotation
2301     // add any new annotation
2302
2303     // intersect alignment annotation with alignment groups
2304
2305     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
2306     List<SequenceGroup> oldrfs = new ArrayList<>();
2307     if (aan != null)
2308     {
2309       for (int an = 0; an < aan.length; an++)
2310       {
2311         if (aan[an].autoCalculated && aan[an].groupRef != null)
2312         {
2313           oldrfs.add(aan[an].groupRef);
2314           alignment.deleteAnnotation(aan[an], false);
2315         }
2316       }
2317     }
2318     if (alignment.getGroups() != null)
2319     {
2320       for (SequenceGroup sg : alignment.getGroups())
2321       {
2322         updateCalcs = false;
2323         if (applyGlobalSettings
2324                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
2325         {
2326           // set defaults for this group's conservation/consensus
2327           sg.setshowSequenceLogo(showprf);
2328           sg.setShowConsensusHistogram(showConsHist);
2329           sg.setNormaliseSequenceLogo(normLogo);
2330           sg.setShowHMMSequenceLogo(showHMMPrf);
2331           sg.setShowInformationHistogram(showInfoHist);
2332           sg.setNormaliseHMMSequenceLogo(normHMMLogo);
2333         }
2334         if (conv)
2335         {
2336           updateCalcs = true;
2337           alignment.addAnnotation(sg.getConservationRow(), 0);
2338         }
2339         if (cons)
2340         {
2341           updateCalcs = true;
2342           alignment.addAnnotation(sg.getConsensus(), 0);
2343         }
2344         // refresh the annotation rows
2345         if (updateCalcs)
2346         {
2347           sg.recalcConservation();
2348         }
2349       }
2350     }
2351     oldrfs.clear();
2352   }
2353
2354   @Override
2355   public boolean isDisplayReferenceSeq()
2356   {
2357     return alignment.hasSeqrep() && viewStyle.isDisplayReferenceSeq();
2358   }
2359
2360   @Override
2361   public void setDisplayReferenceSeq(boolean displayReferenceSeq)
2362   {
2363     viewStyle.setDisplayReferenceSeq(displayReferenceSeq);
2364   }
2365
2366   @Override
2367   public boolean isColourByReferenceSeq()
2368   {
2369     return alignment.hasSeqrep() && viewStyle.isColourByReferenceSeq();
2370   }
2371
2372   @Override
2373   public Color getSequenceColour(SequenceI seq)
2374   {
2375     Color sqc = sequenceColours.get(seq);
2376     return (sqc == null ? Color.white : sqc);
2377   }
2378
2379   @Override
2380   public void setSequenceColour(SequenceI seq, Color col)
2381   {
2382     if (col == null)
2383     {
2384       sequenceColours.remove(seq);
2385     }
2386     else
2387     {
2388       sequenceColours.put(seq, col);
2389     }
2390   }
2391
2392   @Override
2393   public void updateSequenceIdColours()
2394   {
2395     for (SequenceGroup sg : alignment.getGroups())
2396     {
2397       if (sg.idColour != null)
2398       {
2399         for (SequenceI s : sg.getSequences(getHiddenRepSequences()))
2400         {
2401           sequenceColours.put(s, sg.idColour);
2402         }
2403       }
2404     }
2405   }
2406
2407   @Override
2408   public void clearSequenceColours()
2409   {
2410     sequenceColours.clear();
2411   }
2412
2413   @Override
2414   public AlignViewportI getCodingComplement()
2415   {
2416     return this.codingComplement;
2417   }
2418
2419   /**
2420    * Set this as the (cDna/protein) complement of the given viewport. Also
2421    * ensures the reverse relationship is set on the given viewport.
2422    */
2423   @Override
2424   public void setCodingComplement(AlignViewportI av)
2425   {
2426     if (this == av)
2427     {
2428       System.err.println("Ignoring recursive setCodingComplement request");
2429     }
2430     else
2431     {
2432       this.codingComplement = av;
2433       // avoid infinite recursion!
2434       if (av.getCodingComplement() != this)
2435       {
2436         av.setCodingComplement(this);
2437       }
2438     }
2439   }
2440
2441   @Override
2442   public boolean isNucleotide()
2443   {
2444     return getAlignment() == null ? false : getAlignment().isNucleotide();
2445   }
2446
2447   @Override
2448   public FeaturesDisplayedI getFeaturesDisplayed()
2449   {
2450     return featuresDisplayed;
2451   }
2452
2453   @Override
2454   public void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI)
2455   {
2456     featuresDisplayed = featuresDisplayedI;
2457   }
2458
2459   @Override
2460   public boolean areFeaturesDisplayed()
2461   {
2462     return featuresDisplayed != null
2463             && featuresDisplayed.getRegisteredFeaturesCount() > 0;
2464   }
2465
2466   /**
2467    * set the flag
2468    * 
2469    * @param b
2470    *          features are displayed if true
2471    */
2472   @Override
2473   public void setShowSequenceFeatures(boolean b)
2474   {
2475     viewStyle.setShowSequenceFeatures(b);
2476   }
2477
2478   @Override
2479   public boolean isShowSequenceFeatures()
2480   {
2481     return viewStyle.isShowSequenceFeatures();
2482   }
2483
2484   @Override
2485   public void setShowSequenceFeaturesHeight(boolean selected)
2486   {
2487     viewStyle.setShowSequenceFeaturesHeight(selected);
2488   }
2489
2490   @Override
2491   public boolean isShowSequenceFeaturesHeight()
2492   {
2493     return viewStyle.isShowSequenceFeaturesHeight();
2494   }
2495
2496   @Override
2497   public void setShowAnnotation(boolean b)
2498   {
2499     viewStyle.setShowAnnotation(b);
2500   }
2501
2502   @Override
2503   public boolean isShowAnnotation()
2504   {
2505     return viewStyle.isShowAnnotation();
2506   }
2507
2508   @Override
2509   public boolean isRightAlignIds()
2510   {
2511     return viewStyle.isRightAlignIds();
2512   }
2513
2514   @Override
2515   public void setRightAlignIds(boolean rightAlignIds)
2516   {
2517     viewStyle.setRightAlignIds(rightAlignIds);
2518   }
2519
2520   @Override
2521   public boolean getConservationSelected()
2522   {
2523     return viewStyle.getConservationSelected();
2524   }
2525
2526   @Override
2527   public void setShowBoxes(boolean state)
2528   {
2529     viewStyle.setShowBoxes(state);
2530   }
2531
2532   /**
2533    * @return
2534    * @see jalview.api.ViewStyleI#getTextColour()
2535    */
2536   @Override
2537   public Color getTextColour()
2538   {
2539     return viewStyle.getTextColour();
2540   }
2541
2542   /**
2543    * @return
2544    * @see jalview.api.ViewStyleI#getTextColour2()
2545    */
2546   @Override
2547   public Color getTextColour2()
2548   {
2549     return viewStyle.getTextColour2();
2550   }
2551
2552   /**
2553    * @return
2554    * @see jalview.api.ViewStyleI#getThresholdTextColour()
2555    */
2556   @Override
2557   public int getThresholdTextColour()
2558   {
2559     return viewStyle.getThresholdTextColour();
2560   }
2561
2562   /**
2563    * @return
2564    * @see jalview.api.ViewStyleI#isConservationColourSelected()
2565    */
2566   @Override
2567   public boolean isConservationColourSelected()
2568   {
2569     return viewStyle.isConservationColourSelected();
2570   }
2571
2572   /**
2573    * @return
2574    * @see jalview.api.ViewStyleI#isRenderGaps()
2575    */
2576   @Override
2577   public boolean isRenderGaps()
2578   {
2579     return viewStyle.isRenderGaps();
2580   }
2581
2582   /**
2583    * @return
2584    * @see jalview.api.ViewStyleI#isShowColourText()
2585    */
2586   @Override
2587   public boolean isShowColourText()
2588   {
2589     return viewStyle.isShowColourText();
2590   }
2591
2592   /**
2593    * @param conservationColourSelected
2594    * @see jalview.api.ViewStyleI#setConservationColourSelected(boolean)
2595    */
2596   @Override
2597   public void setConservationColourSelected(
2598           boolean conservationColourSelected)
2599   {
2600     viewStyle.setConservationColourSelected(conservationColourSelected);
2601   }
2602
2603   /**
2604    * @param showColourText
2605    * @see jalview.api.ViewStyleI#setShowColourText(boolean)
2606    */
2607   @Override
2608   public void setShowColourText(boolean showColourText)
2609   {
2610     viewStyle.setShowColourText(showColourText);
2611   }
2612
2613   /**
2614    * @param textColour
2615    * @see jalview.api.ViewStyleI#setTextColour(java.awt.Color)
2616    */
2617   @Override
2618   public void setTextColour(Color textColour)
2619   {
2620     viewStyle.setTextColour(textColour);
2621   }
2622
2623   /**
2624    * @param thresholdTextColour
2625    * @see jalview.api.ViewStyleI#setThresholdTextColour(int)
2626    */
2627   @Override
2628   public void setThresholdTextColour(int thresholdTextColour)
2629   {
2630     viewStyle.setThresholdTextColour(thresholdTextColour);
2631   }
2632
2633   /**
2634    * @param textColour2
2635    * @see jalview.api.ViewStyleI#setTextColour2(java.awt.Color)
2636    */
2637   @Override
2638   public void setTextColour2(Color textColour2)
2639   {
2640     viewStyle.setTextColour2(textColour2);
2641   }
2642
2643   @Override
2644   public ViewStyleI getViewStyle()
2645   {
2646     return new ViewStyle(viewStyle);
2647   }
2648
2649   @Override
2650   public void setViewStyle(ViewStyleI settingsForView)
2651   {
2652     viewStyle = new ViewStyle(settingsForView);
2653     if (residueShading != null)
2654     {
2655       residueShading.setConservationApplied(
2656               settingsForView.isConservationColourSelected());
2657     }
2658   }
2659
2660   @Override
2661   public boolean sameStyle(ViewStyleI them)
2662   {
2663     return viewStyle.sameStyle(them);
2664   }
2665
2666   /**
2667    * @return
2668    * @see jalview.api.ViewStyleI#getIdWidth()
2669    */
2670   @Override
2671   public int getIdWidth()
2672   {
2673     return viewStyle.getIdWidth();
2674   }
2675
2676   /**
2677    * @param i
2678    * @see jalview.api.ViewStyleI#setIdWidth(int)
2679    */
2680   @Override
2681   public void setIdWidth(int i)
2682   {
2683     viewStyle.setIdWidth(i);
2684   }
2685
2686   /**
2687    * @return
2688    * @see jalview.api.ViewStyleI#isCentreColumnLabels()
2689    */
2690   @Override
2691   public boolean isCentreColumnLabels()
2692   {
2693     return viewStyle.isCentreColumnLabels();
2694   }
2695
2696   /**
2697    * @param centreColumnLabels
2698    * @see jalview.api.ViewStyleI#setCentreColumnLabels(boolean)
2699    */
2700   @Override
2701   public void setCentreColumnLabels(boolean centreColumnLabels)
2702   {
2703     viewStyle.setCentreColumnLabels(centreColumnLabels);
2704   }
2705
2706   /**
2707    * @param showdbrefs
2708    * @see jalview.api.ViewStyleI#setShowDBRefs(boolean)
2709    */
2710   @Override
2711   public void setShowDBRefs(boolean showdbrefs)
2712   {
2713     viewStyle.setShowDBRefs(showdbrefs);
2714   }
2715
2716   /**
2717    * @return
2718    * @see jalview.api.ViewStyleI#isShowDBRefs()
2719    */
2720   @Override
2721   public boolean isShowDBRefs()
2722   {
2723     return viewStyle.isShowDBRefs();
2724   }
2725
2726   /**
2727    * @return
2728    * @see jalview.api.ViewStyleI#isShowNPFeats()
2729    */
2730   @Override
2731   public boolean isShowNPFeats()
2732   {
2733     return viewStyle.isShowNPFeats();
2734   }
2735
2736   /**
2737    * @param shownpfeats
2738    * @see jalview.api.ViewStyleI#setShowNPFeats(boolean)
2739    */
2740   @Override
2741   public void setShowNPFeats(boolean shownpfeats)
2742   {
2743     viewStyle.setShowNPFeats(shownpfeats);
2744   }
2745
2746   public abstract StructureSelectionManager getStructureSelectionManager();
2747
2748   /**
2749    * Add one command to the command history list.
2750    * 
2751    * @param command
2752    */
2753   public void addToHistoryList(CommandI command)
2754   {
2755     if (this.historyList != null)
2756     {
2757       this.historyList.push(command);
2758       broadcastCommand(command, false);
2759     }
2760   }
2761
2762   protected void broadcastCommand(CommandI command, boolean undo)
2763   {
2764     getStructureSelectionManager().commandPerformed(command, undo,
2765             getVamsasSource());
2766   }
2767
2768   /**
2769    * Add one command to the command redo list.
2770    * 
2771    * @param command
2772    */
2773   public void addToRedoList(CommandI command)
2774   {
2775     if (this.redoList != null)
2776     {
2777       this.redoList.push(command);
2778     }
2779     broadcastCommand(command, true);
2780   }
2781
2782   /**
2783    * Clear the command redo list.
2784    */
2785   public void clearRedoList()
2786   {
2787     if (this.redoList != null)
2788     {
2789       this.redoList.clear();
2790     }
2791   }
2792
2793   public void setHistoryList(Deque<CommandI> list)
2794   {
2795     this.historyList = list;
2796   }
2797
2798   public Deque<CommandI> getHistoryList()
2799   {
2800     return this.historyList;
2801   }
2802
2803   public void setRedoList(Deque<CommandI> list)
2804   {
2805     this.redoList = list;
2806   }
2807
2808   public Deque<CommandI> getRedoList()
2809   {
2810     return this.redoList;
2811   }
2812
2813   @Override
2814   public VamsasSource getVamsasSource()
2815   {
2816     return this;
2817   }
2818
2819   public SequenceAnnotationOrder getSortAnnotationsBy()
2820   {
2821     return sortAnnotationsBy;
2822   }
2823
2824   public void setSortAnnotationsBy(
2825           SequenceAnnotationOrder sortAnnotationsBy)
2826   {
2827     this.sortAnnotationsBy = sortAnnotationsBy;
2828   }
2829
2830   public boolean isShowAutocalculatedAbove()
2831   {
2832     return showAutocalculatedAbove;
2833   }
2834
2835   public void setShowAutocalculatedAbove(boolean showAutocalculatedAbove)
2836   {
2837     this.showAutocalculatedAbove = showAutocalculatedAbove;
2838   }
2839
2840   @Override
2841   public boolean isScaleProteinAsCdna()
2842   {
2843     return viewStyle.isScaleProteinAsCdna();
2844   }
2845
2846   @Override
2847   public void setScaleProteinAsCdna(boolean b)
2848   {
2849     viewStyle.setScaleProteinAsCdna(b);
2850   }
2851
2852   @Override
2853   public boolean isProteinFontAsCdna()
2854   {
2855     return viewStyle.isProteinFontAsCdna();
2856   }
2857
2858   @Override
2859   public void setProteinFontAsCdna(boolean b)
2860   {
2861     viewStyle.setProteinFontAsCdna(b);
2862   }
2863
2864   @Override
2865   public void setShowComplementFeatures(boolean b)
2866   {
2867     viewStyle.setShowComplementFeatures(b);
2868   }
2869
2870   @Override
2871   public boolean isShowComplementFeatures()
2872   {
2873     return viewStyle.isShowComplementFeatures();
2874   }
2875
2876   @Override
2877   public void setShowComplementFeaturesOnTop(boolean b)
2878   {
2879     viewStyle.setShowComplementFeaturesOnTop(b);
2880   }
2881
2882   @Override
2883   public boolean isShowComplementFeaturesOnTop()
2884   {
2885     return viewStyle.isShowComplementFeaturesOnTop();
2886   }
2887
2888   /**
2889    * @return true if view should scroll to show the highlighted region of a
2890    *         sequence
2891    * @return
2892    */
2893   @Override
2894   public final boolean isFollowHighlight()
2895   {
2896     return followHighlight;
2897   }
2898
2899   @Override
2900   public final void setFollowHighlight(boolean b)
2901   {
2902     this.followHighlight = b;
2903   }
2904
2905   @Override
2906   public ViewportRanges getRanges()
2907   {
2908     return ranges;
2909   }
2910
2911   /**
2912    * Helper method to populate the SearchResults with the location in the
2913    * complementary alignment to scroll to, in order to match this one.
2914    * 
2915    * @param sr
2916    *          the SearchResults to add to
2917    * @return the offset (below top of visible region) of the matched sequence
2918    */
2919   protected int findComplementScrollTarget(SearchResultsI sr)
2920   {
2921     final AlignViewportI complement = getCodingComplement();
2922     if (complement == null || !complement.isFollowHighlight())
2923     {
2924       return 0;
2925     }
2926     boolean iAmProtein = !getAlignment().isNucleotide();
2927     AlignmentI proteinAlignment = iAmProtein ? getAlignment()
2928             : complement.getAlignment();
2929     if (proteinAlignment == null)
2930     {
2931       return 0;
2932     }
2933     final List<AlignedCodonFrame> mappings = proteinAlignment
2934             .getCodonFrames();
2935
2936     /*
2937      * Heuristic: find the first mapped sequence (if any) with a non-gapped
2938      * residue in the middle column of the visible region. Scroll the
2939      * complementary alignment to line up the corresponding residue.
2940      */
2941     int seqOffset = 0;
2942     SequenceI sequence = null;
2943
2944     /*
2945      * locate 'middle' column (true middle if an odd number visible, left of
2946      * middle if an even number visible)
2947      */
2948     int middleColumn = ranges.getStartRes()
2949             + (ranges.getEndRes() - ranges.getStartRes()) / 2;
2950     final HiddenSequences hiddenSequences = getAlignment()
2951             .getHiddenSequences();
2952
2953     /*
2954      * searching to the bottom of the alignment gives smoother scrolling across
2955      * all gapped visible regions
2956      */
2957     int lastSeq = alignment.getHeight() - 1;
2958     List<AlignedCodonFrame> seqMappings = null;
2959     for (int seqNo = ranges
2960             .getStartSeq(); seqNo <= lastSeq; seqNo++, seqOffset++)
2961     {
2962       sequence = getAlignment().getSequenceAt(seqNo);
2963       if (hiddenSequences != null && hiddenSequences.isHidden(sequence))
2964       {
2965         continue;
2966       }
2967       if (Comparison.isGap(sequence.getCharAt(middleColumn)))
2968       {
2969         continue;
2970       }
2971       seqMappings = MappingUtils.findMappingsForSequenceAndOthers(sequence,
2972               mappings,
2973               getCodingComplement().getAlignment().getSequences());
2974       if (!seqMappings.isEmpty())
2975       {
2976         break;
2977       }
2978     }
2979
2980     if (sequence == null || seqMappings == null || seqMappings.isEmpty())
2981     {
2982       /*
2983        * No ungapped mapped sequence in middle column - do nothing
2984        */
2985       return 0;
2986     }
2987     MappingUtils.addSearchResults(sr, sequence,
2988             sequence.findPosition(middleColumn), seqMappings);
2989     return seqOffset;
2990   }
2991
2992   /**
2993    * synthesize a column selection if none exists so it covers the given
2994    * selection group. if wholewidth is false, no column selection is made if the
2995    * selection group covers the whole alignment width.
2996    * 
2997    * @param sg
2998    * @param wholewidth
2999    */
3000   public void expandColSelection(SequenceGroup sg, boolean wholewidth)
3001   {
3002     int sgs, sge;
3003     if (sg != null && (sgs = sg.getStartRes()) >= 0
3004             && sg.getStartRes() <= (sge = sg.getEndRes())
3005             && !this.hasSelectedColumns())
3006     {
3007       if (!wholewidth && alignment.getWidth() == (1 + sge - sgs))
3008       {
3009         // do nothing
3010         return;
3011       }
3012       if (colSel == null)
3013       {
3014         colSel = new ColumnSelection();
3015       }
3016       for (int cspos = sg.getStartRes(); cspos <= sg.getEndRes(); cspos++)
3017       {
3018         colSel.addElement(cspos);
3019       }
3020     }
3021   }
3022
3023   /**
3024    * hold status of current selection group - defined on alignment or not.
3025    */
3026   private boolean selectionIsDefinedGroup = false;
3027
3028   @Override
3029   public boolean isSelectionDefinedGroup()
3030   {
3031     if (selectionGroup == null)
3032     {
3033       return false;
3034     }
3035     if (isSelectionGroupChanged(true))
3036     {
3037       selectionIsDefinedGroup = false;
3038       List<SequenceGroup> gps = alignment.getGroups();
3039       if (gps == null || gps.size() == 0)
3040       {
3041         selectionIsDefinedGroup = false;
3042       }
3043       else
3044       {
3045         selectionIsDefinedGroup = gps.contains(selectionGroup);
3046       }
3047     }
3048     return selectionGroup.isDefined() || selectionIsDefinedGroup;
3049   }
3050
3051   /**
3052    * null, or currently highlighted results on this view
3053    */
3054   private SearchResultsI searchResults = null;
3055
3056   protected TreeModel currentTree = null;
3057
3058   @Override
3059   public boolean hasSearchResults()
3060   {
3061     return searchResults != null;
3062   }
3063
3064   @Override
3065   public void setSearchResults(SearchResultsI results)
3066   {
3067     searchResults = results;
3068   }
3069
3070   @Override
3071   public SearchResultsI getSearchResults()
3072   {
3073     return searchResults;
3074   }
3075
3076   /**
3077    * get the consensus sequence as displayed under the PID consensus annotation
3078    * row.
3079    * 
3080    * @return consensus sequence as a new sequence object
3081    */
3082   public SequenceI getConsensusSeq()
3083   {
3084     if (consensus == null)
3085     {
3086       updateConsensus(null);
3087     }
3088     if (consensus == null)
3089     {
3090       return null;
3091     }
3092     StringBuffer seqs = new StringBuffer();
3093     for (int i = 0; i < consensus.annotations.length; i++)
3094     {
3095       Annotation annotation = consensus.annotations[i];
3096       if (annotation != null)
3097       {
3098         String description = annotation.description;
3099         if (description != null && description.startsWith("["))
3100         {
3101           // consensus is a tie - just pick the first one
3102           seqs.append(description.charAt(1));
3103         }
3104         else
3105         {
3106           seqs.append(annotation.displayCharacter);
3107         }
3108       }
3109     }
3110
3111     SequenceI sq = new Sequence("Consensus", seqs.toString());
3112     sq.setDescription("Percentage Identity Consensus "
3113             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
3114     return sq;
3115   }
3116
3117   public boolean hasReferenceAnnotation()
3118   {
3119     AlignmentAnnotation[] annots = this.alignment.getAlignmentAnnotation();
3120     for (AlignmentAnnotation annot : annots)
3121     {
3122       if ("RF".equals(annot.label) || annot.label.contains("Reference"))
3123       {
3124         return true;
3125       }
3126     }
3127     return false;
3128   }
3129
3130   @Override
3131   public void setCurrentTree(TreeModel tree)
3132   {
3133     currentTree = tree;
3134   }
3135
3136   @Override
3137   public TreeModel getCurrentTree()
3138   {
3139     return currentTree;
3140   }
3141
3142   @Override
3143   public AlignmentExportData getAlignExportData(AlignExportSettingsI options)
3144   {
3145     AlignmentI alignmentToExport = null;
3146     String[] omitHidden = null;
3147     alignmentToExport = null;
3148
3149     if (hasHiddenColumns() && !options.isExportHiddenColumns())
3150     {
3151       omitHidden = getViewAsString(false,
3152               options.isExportHiddenSequences());
3153     }
3154
3155     int[] alignmentStartEnd = new int[2];
3156     if (hasHiddenRows() && options.isExportHiddenSequences())
3157     {
3158       alignmentToExport = getAlignment().getHiddenSequences()
3159               .getFullAlignment();
3160     }
3161     else
3162     {
3163       alignmentToExport = getAlignment();
3164     }
3165     alignmentStartEnd = getAlignment().getHiddenColumns()
3166             .getVisibleStartAndEndIndex(alignmentToExport.getWidth());
3167     AlignmentExportData ed = new AlignmentExportData(alignmentToExport,
3168             omitHidden, alignmentStartEnd);
3169     return ed;
3170   }
3171   
3172   @Override
3173   public boolean isNormaliseSequenceLogo()
3174   {
3175     return normaliseSequenceLogo;
3176   }
3177
3178   public void setNormaliseSequenceLogo(boolean state)
3179   {
3180     normaliseSequenceLogo = state;
3181   }
3182
3183   @Override
3184   public boolean isNormaliseHMMSequenceLogo()
3185   {
3186     return hmmNormaliseSequenceLogo;
3187   }
3188
3189   public void setNormaliseHMMSequenceLogo(boolean state)
3190   {
3191     hmmNormaliseSequenceLogo = state;
3192   }
3193   /**
3194    * flag set to indicate if structure views might be out of sync with sequences
3195    * in the alignment
3196    */
3197
3198   private boolean needToUpdateStructureViews = false;
3199
3200   @Override
3201   public boolean isUpdateStructures()
3202   {
3203     return needToUpdateStructureViews;
3204   }
3205
3206   @Override
3207   public void setUpdateStructures(boolean update)
3208   {
3209     needToUpdateStructureViews = update;
3210   }
3211
3212   @Override
3213   public boolean needToUpdateStructureViews()
3214   {
3215     boolean update = needToUpdateStructureViews;
3216     needToUpdateStructureViews = false;
3217     return update;
3218   }
3219
3220   @Override
3221   public void addSequenceGroup(SequenceGroup sequenceGroup)
3222   {
3223     alignment.addGroup(sequenceGroup);
3224
3225     Color col = sequenceGroup.idColour;
3226     if (col != null)
3227     {
3228       col = col.brighter();
3229
3230       for (SequenceI sq : sequenceGroup.getSequences())
3231       {
3232         setSequenceColour(sq, col);
3233       }
3234     }
3235
3236     if (codingComplement != null)
3237     {
3238       SequenceGroup mappedGroup = MappingUtils
3239               .mapSequenceGroup(sequenceGroup, this, codingComplement);
3240       if (mappedGroup.getSequences().size() > 0)
3241       {
3242         codingComplement.getAlignment().addGroup(mappedGroup);
3243
3244         if (col != null)
3245         {
3246           for (SequenceI seq : mappedGroup.getSequences())
3247           {
3248             codingComplement.setSequenceColour(seq, col);
3249           }
3250         }
3251       }
3252       // propagate the structure view update flag according to our own setting
3253       codingComplement.setUpdateStructures(needToUpdateStructureViews);
3254     }
3255   }
3256   /**
3257    * Filters out sequences with an eValue higher than the specified value. The
3258    * filtered sequences are hidden or deleted. Sequences with no eValues are also
3259    * filtered out.
3260    * 
3261    * @param eValue
3262    * @param delete
3263    */
3264   public void filterByEvalue(double eValue)
3265   {
3266     for (SequenceI seq : alignment.getSequencesArray())
3267     {
3268       if ((seq.getAnnotation("Search Scores") == null
3269               || seq.getAnnotation("Search Scores")[0].getEValue() > eValue)
3270               && seq.getHMM() == null)
3271       {
3272         hideSequence(new SequenceI[] { seq });
3273       }
3274     }
3275   }
3276
3277   /**
3278    * Filters out sequences with an score lower than the specified value. The
3279    * filtered sequences are hidden or deleted.
3280    * 
3281    * @param score
3282    * @param delete
3283    */
3284   public void filterByScore(double score)
3285   {
3286     for (SequenceI seq : alignment.getSequencesArray())
3287     {
3288       if ((seq.getAnnotation("Search Scores") == null
3289               || seq.getAnnotation("Search Scores")[0]
3290                       .getBitScore() < score)
3291               && seq.getHMM() == null)
3292       {
3293         hideSequence(new SequenceI[] { seq });
3294       }
3295     }
3296   }  
3297
3298   /**
3299    * Notify TreePanel and AlignmentPanel of some sort of alignment change.
3300    */
3301   public void notifyAlignment()
3302   {
3303     changeSupport.firePropertyChange(PROPERTY_ALIGNMENT, null, alignment.getSequences());
3304   }
3305   
3306   /**
3307    * Notify AlignmentPanel of a sequence column selection or visibility changes.
3308    */
3309   public void notifySequence()
3310   {
3311     changeSupport.firePropertyChange(PROPERTY_SEQUENCE, null, null);
3312   }
3313 }