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