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