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