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