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