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