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