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