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