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