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