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