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