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