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