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