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