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