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