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