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