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