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