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