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