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