JAL-1370 JAL-1371 flags for display and colour of view with/without reference sequence
[jalview.git] / src / jalview / viewmodel / AlignmentViewport.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.viewmodel;
19
20 import jalview.analysis.AAFrequency;
21 import jalview.analysis.Conservation;
22 import jalview.api.AlignCalcManagerI;
23 import jalview.api.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.datamodel.AlignmentAnnotation;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.AlignmentView;
28 import jalview.datamodel.Annotation;
29 import jalview.datamodel.ColumnSelection;
30 import jalview.datamodel.Sequence;
31 import jalview.datamodel.SequenceCollectionI;
32 import jalview.datamodel.SequenceGroup;
33 import jalview.datamodel.SequenceI;
34 import jalview.schemes.Blosum62ColourScheme;
35 import jalview.schemes.ClustalxColourScheme;
36 import jalview.schemes.ColourSchemeI;
37 import jalview.schemes.PIDColourScheme;
38 import jalview.schemes.ResidueProperties;
39 import jalview.workers.AlignCalcManager;
40 import jalview.workers.ConsensusThread;
41 import jalview.workers.StrucConsensusThread;
42
43 import java.util.ArrayList;
44 import java.util.Hashtable;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Vector;
48
49 /**
50  * base class holding visualization and analysis attributes and common logic for
51  * an active alignment view displayed in the GUI
52  * 
53  * @author jimp
54  * 
55  */
56 public abstract class AlignmentViewport implements AlignViewportI
57 {
58   /**
59    * alignment displayed in the viewport. Please use get/setter
60    */
61   protected AlignmentI alignment;
62
63   protected String sequenceSetID;
64
65   /**
66    * probably unused indicator that view is of a dataset rather than an
67    * alignment
68    */
69   protected boolean isDataset = false;
70
71   private Map<SequenceI, SequenceCollectionI> hiddenRepSequences;
72
73   protected ColumnSelection colSel = new ColumnSelection();
74
75   public boolean autoCalculateConsensus = true;
76
77   protected boolean autoCalculateStrucConsensus = true;
78
79   protected boolean ignoreGapsInConsensusCalculation = false;
80
81   protected ColourSchemeI globalColourScheme = null;
82
83   /**
84    * gui state - changes to colour scheme propagated to all groups
85    */
86   private boolean colourAppliesToAllGroups;
87
88   /**
89    * @param value
90    *          indicating if subsequent colourscheme changes will be propagated
91    *          to all groups
92    */
93   public void setColourAppliesToAllGroups(boolean b)
94   {
95     colourAppliesToAllGroups = b;
96   }
97
98   /**
99    * 
100    * 
101    * @return flag indicating if colourchanges propagated to all groups
102    */
103   public boolean getColourAppliesToAllGroups()
104   {
105     return colourAppliesToAllGroups;
106   }
107
108   boolean abovePIDThreshold = false;
109
110   /**
111    * GUI state
112    * 
113    * @return true if percent identity threshold is applied to shading
114    */
115   public boolean getAbovePIDThreshold()
116   {
117     return abovePIDThreshold;
118   }
119
120   /**
121    * GUI state
122    * 
123    * 
124    * @param b
125    *          indicate if percent identity threshold is applied to shading
126    */
127   public void setAbovePIDThreshold(boolean b)
128   {
129     abovePIDThreshold = b;
130   }
131
132   int threshold;
133
134   /**
135    * DOCUMENT ME!
136    * 
137    * @param thresh
138    *          DOCUMENT ME!
139    */
140   public void setThreshold(int thresh)
141   {
142     threshold = thresh;
143   }
144
145   /**
146    * DOCUMENT ME!
147    * 
148    * @return DOCUMENT ME!
149    */
150   public int getThreshold()
151   {
152     return threshold;
153   }
154
155   int increment;
156
157   /**
158    * 
159    * @param inc
160    *          set the scalar for bleaching colourschemes according to degree of
161    *          conservation
162    */
163   public void setIncrement(int inc)
164   {
165     increment = inc;
166   }
167
168   /**
169    * GUI State
170    * 
171    * @return get scalar for bleaching colourschemes by conservation
172    */
173   public int getIncrement()
174   {
175     return increment;
176   }
177
178   boolean conservationColourSelected = false;
179
180   /**
181    * GUI state
182    * 
183    * @return true if conservation based shading is enabled
184    */
185   public boolean getConservationSelected()
186   {
187     return conservationColourSelected;
188   }
189
190   /**
191    * GUI state
192    * 
193    * @param b
194    *          enable conservation based shading
195    */
196   public void setConservationSelected(boolean b)
197   {
198     conservationColourSelected = b;
199   }
200
201   @Override
202   public void setGlobalColourScheme(ColourSchemeI cs)
203   {
204     // TODO: logic refactored from AlignFrame changeColour -
205     // autorecalc stuff should be changed to rely on the worker system
206     // check to see if we should implement a changeColour(cs) method rather than
207     // put th logic in here
208     // - means that caller decides if they want to just modify state and defer
209     // calculation till later or to do all calculations in thread.
210     // via changecolour
211     globalColourScheme = cs;
212     if (getColourAppliesToAllGroups())
213     {
214       for (SequenceGroup sg : getAlignment().getGroups())
215       {
216         if (cs == null)
217         {
218           sg.cs = null;
219           continue;
220         }
221         if (cs instanceof ClustalxColourScheme)
222         {
223           sg.cs = new ClustalxColourScheme(sg, getHiddenRepSequences());
224         }
225         else
226         {
227           try
228           {
229             sg.cs = cs.getClass().newInstance();
230           } catch (Exception ex)
231           {
232             ex.printStackTrace();
233             sg.cs = cs;
234           }
235         }
236
237         if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
238                 || cs instanceof Blosum62ColourScheme)
239         {
240           sg.cs.setThreshold(threshold, getIgnoreGapsConsensus());
241           sg.cs.setConsensus(AAFrequency.calculate(
242                   sg.getSequences(getHiddenRepSequences()), 0,
243                   sg.getWidth()));
244         }
245         else
246         {
247           sg.cs.setThreshold(0, getIgnoreGapsConsensus());
248         }
249
250         if (getConservationSelected())
251         {
252           Conservation c = new Conservation("Group",
253                   ResidueProperties.propHash, 3,
254                   sg.getSequences(getHiddenRepSequences()), 0,
255                   getAlignment().getWidth() - 1);
256           c.calculate();
257           c.verdict(false, getConsPercGaps());
258           sg.cs.setConservation(c);
259         }
260         else
261         {
262           sg.cs.setConservation(null);
263           sg.cs.setThreshold(0, getIgnoreGapsConsensus());
264         }
265
266       }
267     }
268
269   }
270
271   @Override
272   public ColourSchemeI getGlobalColourScheme()
273   {
274     return globalColourScheme;
275   }
276
277   protected AlignmentAnnotation consensus;
278
279   protected AlignmentAnnotation strucConsensus;
280
281   protected AlignmentAnnotation conservation;
282
283   protected AlignmentAnnotation quality;
284
285   protected AlignmentAnnotation[] groupConsensus;
286
287   protected AlignmentAnnotation[] groupConservation;
288
289   /**
290    * results of alignment consensus analysis for visible portion of view
291    */
292   protected Hashtable[] hconsensus = null;
293
294   /**
295    * results of secondary structure base pair consensus for visible portion of
296    * view
297    */
298   protected Hashtable[] hStrucConsensus = null;
299
300   /**
301    * percentage gaps allowed in a column before all amino acid properties should
302    * be considered unconserved
303    */
304   int ConsPercGaps = 25; // JBPNote : This should be a scalable property!
305
306   @Override
307   public int getConsPercGaps()
308   {
309     return ConsPercGaps;
310   }
311
312   @Override
313   public void setSequenceConsensusHash(Hashtable[] hconsensus)
314   {
315     this.hconsensus = hconsensus;
316
317   }
318
319   @Override
320   public Hashtable[] getSequenceConsensusHash()
321   {
322     return hconsensus;
323   }
324
325   @Override
326   public Hashtable[] getRnaStructureConsensusHash()
327   {
328     return hStrucConsensus;
329   }
330
331   @Override
332   public void setRnaStructureConsensusHash(Hashtable[] hStrucConsensus)
333   {
334     this.hStrucConsensus = hStrucConsensus;
335
336   }
337
338   @Override
339   public AlignmentAnnotation getAlignmentQualityAnnot()
340   {
341     return quality;
342   }
343
344   @Override
345   public AlignmentAnnotation getAlignmentConservationAnnotation()
346   {
347     return conservation;
348   }
349
350   @Override
351   public AlignmentAnnotation getAlignmentConsensusAnnotation()
352   {
353     return consensus;
354   }
355
356   @Override
357   public AlignmentAnnotation getAlignmentStrucConsensusAnnotation()
358   {
359     return strucConsensus;
360   }
361
362   protected AlignCalcManagerI calculator = new AlignCalcManager();
363
364   /**
365    * trigger update of conservation annotation
366    */
367   public void updateConservation(final AlignmentViewPanel ap)
368   {
369     // see note in mantis : issue number 8585
370     if (alignment.isNucleotide() || conservation == null
371             || !autoCalculateConsensus)
372     {
373       return;
374     }
375     if (calculator
376             .getRegisteredWorkersOfClass(jalview.workers.ConservationThread.class) == null)
377     {
378       calculator.registerWorker(new jalview.workers.ConservationThread(
379               this, ap));
380     }
381   }
382
383   /**
384    * trigger update of consensus annotation
385    */
386   public void updateConsensus(final AlignmentViewPanel ap)
387   {
388     // see note in mantis : issue number 8585
389     if (consensus == null || !autoCalculateConsensus)
390     {
391       return;
392     }
393     if (calculator.getRegisteredWorkersOfClass(ConsensusThread.class) == null)
394     {
395       calculator.registerWorker(new ConsensusThread(this, ap));
396     }
397   }
398
399   // --------START Structure Conservation
400   public void updateStrucConsensus(final AlignmentViewPanel ap)
401   {
402     if (autoCalculateStrucConsensus && strucConsensus == null
403             && alignment.isNucleotide() && alignment.hasRNAStructure())
404     {
405       // secondary structure has been added - so init the consensus line
406       initRNAStructure();
407     }
408
409     // see note in mantis : issue number 8585
410     if (strucConsensus == null || !autoCalculateStrucConsensus)
411     {
412       return;
413     }
414     if (calculator.getRegisteredWorkersOfClass(StrucConsensusThread.class) == null)
415     {
416       calculator.registerWorker(new StrucConsensusThread(this, ap));
417     }
418   }
419
420   public boolean isCalcInProgress()
421   {
422     return calculator.isWorking();
423   }
424
425   @Override
426   public boolean isCalculationInProgress(
427           AlignmentAnnotation alignmentAnnotation)
428   {
429     if (!alignmentAnnotation.autoCalculated)
430       return false;
431     if (calculator.workingInvolvedWith(alignmentAnnotation))
432     {
433       // System.err.println("grey out ("+alignmentAnnotation.label+")");
434       return true;
435     }
436     return false;
437   }
438
439   @Override
440   public boolean isClosed()
441   {
442     // TODO: check that this isClosed is only true after panel is closed, not
443     // before it is fully constructed.
444     return alignment == null;
445   }
446
447   @Override
448   public AlignCalcManagerI getCalcManager()
449   {
450     return calculator;
451   }
452
453   /**
454    * should conservation rows be shown for groups
455    */
456   protected boolean showGroupConservation = false;
457
458   /**
459    * should consensus rows be shown for groups
460    */
461   protected boolean showGroupConsensus = false;
462
463   /**
464    * should consensus profile be rendered by default
465    */
466   protected boolean showSequenceLogo = false;
467
468   /**
469    * should consensus profile be rendered normalised to row height
470    */
471   protected boolean normaliseSequenceLogo = false;
472
473   /**
474    * should consensus histograms be rendered by default
475    */
476   protected boolean showConsensusHistogram = true;
477
478   /**
479    * @return the showConsensusProfile
480    */
481   @Override
482   public boolean isShowSequenceLogo()
483   {
484     return showSequenceLogo;
485   }
486
487   /**
488    * @param showSequenceLogo
489    *          the new value
490    */
491   public void setShowSequenceLogo(boolean showSequenceLogo)
492   {
493     if (showSequenceLogo != this.showSequenceLogo)
494     {
495       // TODO: decouple settings setting from calculation when refactoring
496       // annotation update method from alignframe to viewport
497       this.showSequenceLogo = showSequenceLogo;
498       calculator.updateAnnotationFor(ConsensusThread.class);
499       calculator.updateAnnotationFor(StrucConsensusThread.class);
500     }
501     this.showSequenceLogo = showSequenceLogo;
502   }
503
504   /**
505    * @param showConsensusHistogram
506    *          the showConsensusHistogram to set
507    */
508   public void setShowConsensusHistogram(boolean showConsensusHistogram)
509   {
510     this.showConsensusHistogram = showConsensusHistogram;
511   }
512
513   /**
514    * @return the showGroupConservation
515    */
516   public boolean isShowGroupConservation()
517   {
518     return showGroupConservation;
519   }
520
521   /**
522    * @param showGroupConservation
523    *          the showGroupConservation to set
524    */
525   public void setShowGroupConservation(boolean showGroupConservation)
526   {
527     this.showGroupConservation = showGroupConservation;
528   }
529
530   /**
531    * @return the showGroupConsensus
532    */
533   public boolean isShowGroupConsensus()
534   {
535     return showGroupConsensus;
536   }
537
538   /**
539    * @param showGroupConsensus
540    *          the showGroupConsensus to set
541    */
542   public void setShowGroupConsensus(boolean showGroupConsensus)
543   {
544     this.showGroupConsensus = showGroupConsensus;
545   }
546
547   /**
548    * 
549    * @return flag to indicate if the consensus histogram should be rendered by
550    *         default
551    */
552   @Override
553   public boolean isShowConsensusHistogram()
554   {
555     return this.showConsensusHistogram;
556   }
557
558   /**
559    * show non-conserved residues only
560    */
561   protected boolean showUnconserved = false;
562
563   /**
564    * when set, updateAlignment will always ensure sequences are of equal length
565    */
566   private boolean padGaps = false;
567
568   /**
569    * when set, alignment should be reordered according to a newly opened tree
570    */
571   public boolean sortByTree = false;
572
573   public boolean getShowUnconserved()
574   {
575     return showUnconserved;
576   }
577
578   public void setShowUnconserved(boolean showunconserved)
579   {
580     showUnconserved = showunconserved;
581   }
582
583   /**
584    * @param showNonconserved
585    *          the showUnconserved to set
586    */
587   public void setShowunconserved(boolean displayNonconserved)
588   {
589     this.showUnconserved = displayNonconserved;
590   }
591
592   /**
593    * 
594    * 
595    * @return null or the currently selected sequence region
596    */
597   public SequenceGroup getSelectionGroup()
598   {
599     return selectionGroup;
600   }
601
602   /**
603    * Set the selection group for this window.
604    * 
605    * @param sg
606    *          - group holding references to sequences in this alignment view
607    * 
608    */
609   public void setSelectionGroup(SequenceGroup sg)
610   {
611     selectionGroup = sg;
612   }
613
614   public void setHiddenColumns(ColumnSelection colsel)
615   {
616     this.colSel = colsel;
617     if (colSel.getHiddenColumns() != null)
618     {
619       hasHiddenColumns = true;
620     }
621   }
622
623   @Override
624   public ColumnSelection getColumnSelection()
625   {
626     return colSel;
627   }
628
629   public void setColumnSelection(ColumnSelection colSel)
630   {
631     this.colSel = colSel;
632   }
633
634   /**
635    * 
636    * @return
637    */
638   @Override
639   public Map<SequenceI, SequenceCollectionI> getHiddenRepSequences()
640   {
641     return hiddenRepSequences;
642   }
643
644   @Override
645   public void setHiddenRepSequences(
646           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
647   {
648     this.hiddenRepSequences = hiddenRepSequences;
649   }
650
651   protected boolean hasHiddenColumns = false;
652
653   public void updateHiddenColumns()
654   {
655     hasHiddenColumns = colSel.getHiddenColumns() != null;
656   }
657
658   protected boolean hasHiddenRows = false;
659
660   public boolean hasHiddenRows()
661   {
662     return hasHiddenRows;
663   }
664
665   protected SequenceGroup selectionGroup;
666
667   public void setSequenceSetId(String newid)
668   {
669     if (sequenceSetID != null)
670     {
671       System.err
672               .println("Warning - overwriting a sequenceSetId for a viewport!");
673     }
674     sequenceSetID = new String(newid);
675   }
676
677   public String getSequenceSetId()
678   {
679     if (sequenceSetID == null)
680     {
681       sequenceSetID = alignment.hashCode() + "";
682     }
683
684     return sequenceSetID;
685   }
686
687   /**
688    * unique viewId for synchronizing state (e.g. with stored Jalview Project)
689    * 
690    */
691   protected String viewId = null;
692
693   public String getViewId()
694   {
695     if (viewId == null)
696     {
697       viewId = this.getSequenceSetId() + "." + this.hashCode() + "";
698     }
699     return viewId;
700   }
701
702   public void setIgnoreGapsConsensus(boolean b, AlignmentViewPanel ap)
703   {
704     ignoreGapsInConsensusCalculation = b;
705     if (ap != null)
706     {
707       updateConsensus(ap);
708       if (globalColourScheme != null)
709       {
710         globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
711                 ignoreGapsInConsensusCalculation);
712       }
713     }
714
715   }
716
717   private long sgrouphash = -1, colselhash = -1;
718
719   /**
720    * checks current SelectionGroup against record of last hash value, and
721    * updates record.
722    * 
723    * @param b
724    *          update the record of last hash value
725    * 
726    * @return true if SelectionGroup changed since last call (when b is true)
727    */
728   public boolean isSelectionGroupChanged(boolean b)
729   {
730     int hc = (selectionGroup == null || selectionGroup.getSize() == 0) ? -1
731             : selectionGroup.hashCode();
732     if (hc != -1 && hc != sgrouphash)
733     {
734       if (b)
735       {
736         sgrouphash = hc;
737       }
738       return true;
739     }
740     return false;
741   }
742
743   /**
744    * checks current colsel against record of last hash value, and optionally
745    * updates record.
746    * 
747    * @param b
748    *          update the record of last hash value
749    * @return true if colsel changed since last call (when b is true)
750    */
751   public boolean isColSelChanged(boolean b)
752   {
753     int hc = (colSel == null || colSel.size() == 0) ? -1 : colSel
754             .hashCode();
755     if (hc != -1 && hc != colselhash)
756     {
757       if (b)
758       {
759         colselhash = hc;
760       }
761       return true;
762     }
763     return false;
764   }
765
766   @Override
767   public boolean getIgnoreGapsConsensus()
768   {
769     return ignoreGapsInConsensusCalculation;
770   }
771
772   // / property change stuff
773
774   // JBPNote Prolly only need this in the applet version.
775   private final java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
776           this);
777
778   protected boolean showConservation = true;
779
780   protected boolean showQuality = true;
781
782   protected boolean showConsensus = true;
783
784   /**
785    * Property change listener for changes in alignment
786    * 
787    * @param listener
788    *          DOCUMENT ME!
789    */
790   public void addPropertyChangeListener(
791           java.beans.PropertyChangeListener listener)
792   {
793     changeSupport.addPropertyChangeListener(listener);
794   }
795
796   /**
797    * DOCUMENT ME!
798    * 
799    * @param listener
800    *          DOCUMENT ME!
801    */
802   public void removePropertyChangeListener(
803           java.beans.PropertyChangeListener listener)
804   {
805     changeSupport.removePropertyChangeListener(listener);
806   }
807
808   /**
809    * Property change listener for changes in alignment
810    * 
811    * @param prop
812    *          DOCUMENT ME!
813    * @param oldvalue
814    *          DOCUMENT ME!
815    * @param newvalue
816    *          DOCUMENT ME!
817    */
818   public void firePropertyChange(String prop, Object oldvalue,
819           Object newvalue)
820   {
821     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
822   }
823
824   // common hide/show column stuff
825
826   public void hideSelectedColumns()
827   {
828     if (colSel.size() < 1)
829     {
830       return;
831     }
832
833     colSel.hideSelectedColumns();
834     setSelectionGroup(null);
835
836     hasHiddenColumns = true;
837   }
838
839   public void hideColumns(int start, int end)
840   {
841     if (start == end)
842     {
843       colSel.hideColumns(start);
844     }
845     else
846     {
847       colSel.hideColumns(start, end);
848     }
849
850     hasHiddenColumns = true;
851   }
852
853   public void showColumn(int col)
854   {
855     colSel.revealHiddenColumns(col);
856     if (colSel.getHiddenColumns() == null)
857     {
858       hasHiddenColumns = false;
859     }
860   }
861
862   public void showAllHiddenColumns()
863   {
864     colSel.revealAllHiddenColumns();
865     hasHiddenColumns = false;
866   }
867
868   // common hide/show seq stuff
869   public void showAllHiddenSeqs()
870   {
871     if (alignment.getHiddenSequences().getSize() > 0)
872     {
873       if (selectionGroup == null)
874       {
875         selectionGroup = new SequenceGroup();
876         selectionGroup.setEndRes(alignment.getWidth() - 1);
877       }
878       Vector tmp = alignment.getHiddenSequences().showAll(
879               hiddenRepSequences);
880       for (int t = 0; t < tmp.size(); t++)
881       {
882         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
883       }
884
885       hasHiddenRows = false;
886       hiddenRepSequences = null;
887
888       firePropertyChange("alignment", null, alignment.getSequences());
889       // used to set hasHiddenRows/hiddenRepSequences here, after the property
890       // changed event
891       sendSelection();
892     }
893   }
894
895   public void showSequence(int index)
896   {
897     Vector tmp = alignment.getHiddenSequences().showSequence(index,
898             hiddenRepSequences);
899     if (tmp.size() > 0)
900     {
901       if (selectionGroup == null)
902       {
903         selectionGroup = new SequenceGroup();
904         selectionGroup.setEndRes(alignment.getWidth() - 1);
905       }
906
907       for (int t = 0; t < tmp.size(); t++)
908       {
909         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
910       }
911       // JBPNote: refactor: only update flag if we modified visiblity (used to
912       // do this regardless)
913       if (alignment.getHiddenSequences().getSize() < 1)
914       {
915         hasHiddenRows = false;
916       }
917       firePropertyChange("alignment", null, alignment.getSequences());
918       sendSelection();
919     }
920   }
921
922   public void hideAllSelectedSeqs()
923   {
924     if (selectionGroup == null || selectionGroup.getSize() < 1)
925     {
926       return;
927     }
928
929     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
930
931     hideSequence(seqs);
932
933     setSelectionGroup(null);
934   }
935
936   public void hideSequence(SequenceI[] seq)
937   {
938     if (seq != null)
939     {
940       for (int i = 0; i < seq.length; i++)
941       {
942         alignment.getHiddenSequences().hideSequence(seq[i]);
943       }
944       hasHiddenRows = true;
945       firePropertyChange("alignment", null, alignment.getSequences());
946     }
947   }
948
949   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
950   {
951     int sSize = sg.getSize();
952     if (sSize < 2)
953     {
954       return;
955     }
956
957     if (hiddenRepSequences == null)
958     {
959       hiddenRepSequences = new Hashtable();
960     }
961
962     hiddenRepSequences.put(repSequence, sg);
963
964     // Hide all sequences except the repSequence
965     SequenceI[] seqs = new SequenceI[sSize - 1];
966     int index = 0;
967     for (int i = 0; i < sSize; i++)
968     {
969       if (sg.getSequenceAt(i) != repSequence)
970       {
971         if (index == sSize - 1)
972         {
973           return;
974         }
975
976         seqs[index++] = sg.getSequenceAt(i);
977       }
978     }
979     sg.setSeqrep(repSequence); // note: not done in 2.7applet
980     sg.setHidereps(true); // note: not done in 2.7applet
981     hideSequence(seqs);
982
983   }
984
985   public boolean isHiddenRepSequence(SequenceI seq)
986   {
987     return hiddenRepSequences != null
988             && hiddenRepSequences.containsKey(seq);
989   }
990
991   public SequenceGroup getRepresentedSequences(SequenceI seq)
992   {
993     return (SequenceGroup) (hiddenRepSequences == null ? null
994             : hiddenRepSequences.get(seq));
995   }
996
997   public int adjustForHiddenSeqs(int alignmentIndex)
998   {
999     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1000             alignmentIndex);
1001   }
1002
1003   // Selection manipulation
1004   /**
1005    * broadcast selection to any interested parties
1006    */
1007   public abstract void sendSelection();
1008
1009   public void invertColumnSelection()
1010   {
1011     colSel.invertColumnSelection(0, alignment.getWidth());
1012   }
1013
1014   /**
1015    * This method returns an array of new SequenceI objects derived from the
1016    * whole alignment or just the current selection with start and end points
1017    * adjusted
1018    * 
1019    * @note if you need references to the actual SequenceI objects in the
1020    *       alignment or currently selected then use getSequenceSelection()
1021    * @return selection as new sequenceI objects
1022    */
1023   public SequenceI[] getSelectionAsNewSequence()
1024   {
1025     SequenceI[] sequences;
1026     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1027     // this was the only caller in the applet for this method
1028     // JBPNote: in applet, this method returned references to the alignment
1029     // sequences, and it did not honour the presence/absence of annotation
1030     // attached to the alignment (probably!)
1031     if (selectionGroup == null || selectionGroup.getSize() == 0)
1032     {
1033       sequences = alignment.getSequencesArray();
1034       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1035       for (int i = 0; i < sequences.length; i++)
1036       {
1037         sequences[i] = new Sequence(sequences[i], annots); // construct new
1038         // sequence with
1039         // subset of visible
1040         // annotation
1041       }
1042     }
1043     else
1044     {
1045       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1046     }
1047
1048     return sequences;
1049   }
1050
1051   /**
1052    * get the currently selected sequence objects or all the sequences in the
1053    * alignment.
1054    * 
1055    * @return array of references to sequence objects
1056    */
1057   public SequenceI[] getSequenceSelection()
1058   {
1059     SequenceI[] sequences = null;
1060     if (selectionGroup != null)
1061     {
1062       sequences = selectionGroup.getSequencesInOrder(alignment);
1063     }
1064     if (sequences == null)
1065     {
1066       sequences = alignment.getSequencesArray();
1067     }
1068     return sequences;
1069   }
1070
1071   /**
1072    * This method returns the visible alignment as text, as seen on the GUI, ie
1073    * if columns are hidden they will not be returned in the result. Use this for
1074    * calculating trees, PCA, redundancy etc on views which contain hidden
1075    * columns.
1076    * 
1077    * @return String[]
1078    */
1079   public jalview.datamodel.CigarArray getViewAsCigars(
1080           boolean selectedRegionOnly)
1081   {
1082     return new jalview.datamodel.CigarArray(alignment,
1083             (hasHiddenColumns ? colSel : null),
1084             (selectedRegionOnly ? selectionGroup : null));
1085   }
1086
1087   /**
1088    * return a compact representation of the current alignment selection to pass
1089    * to an analysis function
1090    * 
1091    * @param selectedOnly
1092    *          boolean true to just return the selected view
1093    * @return AlignmentView
1094    */
1095   public jalview.datamodel.AlignmentView getAlignmentView(
1096           boolean selectedOnly)
1097   {
1098     return getAlignmentView(selectedOnly, false);
1099   }
1100
1101   /**
1102    * return a compact representation of the current alignment selection to pass
1103    * to an analysis function
1104    * 
1105    * @param selectedOnly
1106    *          boolean true to just return the selected view
1107    * @param markGroups
1108    *          boolean true to annotate the alignment view with groups on the
1109    *          alignment (and intersecting with selected region if selectedOnly
1110    *          is true)
1111    * @return AlignmentView
1112    */
1113   public jalview.datamodel.AlignmentView getAlignmentView(
1114           boolean selectedOnly, boolean markGroups)
1115   {
1116     return new AlignmentView(alignment, colSel, selectionGroup,
1117             hasHiddenColumns, selectedOnly, markGroups);
1118   }
1119
1120   /**
1121    * This method returns the visible alignment as text, as seen on the GUI, ie
1122    * if columns are hidden they will not be returned in the result. Use this for
1123    * calculating trees, PCA, redundancy etc on views which contain hidden
1124    * columns.
1125    * 
1126    * @return String[]
1127    */
1128   public String[] getViewAsString(boolean selectedRegionOnly)
1129   {
1130     String[] selection = null;
1131     SequenceI[] seqs = null;
1132     int i, iSize;
1133     int start = 0, end = 0;
1134     if (selectedRegionOnly && selectionGroup != null)
1135     {
1136       iSize = selectionGroup.getSize();
1137       seqs = selectionGroup.getSequencesInOrder(alignment);
1138       start = selectionGroup.getStartRes();
1139       end = selectionGroup.getEndRes() + 1;
1140     }
1141     else
1142     {
1143       iSize = alignment.getHeight();
1144       seqs = alignment.getSequencesArray();
1145       end = alignment.getWidth();
1146     }
1147
1148     selection = new String[iSize];
1149     if (hasHiddenColumns)
1150     {
1151       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1152     }
1153     else
1154     {
1155       for (i = 0; i < iSize; i++)
1156       {
1157         selection[i] = seqs[i].getSequenceAsString(start, end);
1158       }
1159
1160     }
1161     return selection;
1162   }
1163
1164   /**
1165    * return visible region boundaries within given column range
1166    * 
1167    * @param min
1168    *          first column (inclusive, from 0)
1169    * @param max
1170    *          last column (exclusive)
1171    * @return int[][] range of {start,end} visible positions
1172    */
1173   public int[][] getVisibleRegionBoundaries(int min, int max)
1174   {
1175     Vector regions = new Vector();
1176     int start = min;
1177     int end = max;
1178
1179     do
1180     {
1181       if (hasHiddenColumns)
1182       {
1183         if (start == 0)
1184         {
1185           start = colSel.adjustForHiddenColumns(start);
1186         }
1187
1188         end = colSel.getHiddenBoundaryRight(start);
1189         if (start == end)
1190         {
1191           end = max;
1192         }
1193         if (end > max)
1194         {
1195           end = max;
1196         }
1197       }
1198
1199       regions.addElement(new int[]
1200       { start, end });
1201
1202       if (hasHiddenColumns)
1203       {
1204         start = colSel.adjustForHiddenColumns(end);
1205         start = colSel.getHiddenBoundaryLeft(start) + 1;
1206       }
1207     } while (end < max);
1208
1209     int[][] startEnd = new int[regions.size()][2];
1210
1211     regions.copyInto(startEnd);
1212
1213     return startEnd;
1214
1215   }
1216
1217   /**
1218    * @return the padGaps
1219    */
1220   public boolean isPadGaps()
1221   {
1222     return padGaps;
1223   }
1224
1225   /**
1226    * @param padGaps
1227    *          the padGaps to set
1228    */
1229   public void setPadGaps(boolean padGaps)
1230   {
1231     this.padGaps = padGaps;
1232   }
1233
1234   /**
1235    * apply any post-edit constraints and trigger any calculations needed after
1236    * an edit has been performed on the alignment
1237    * 
1238    * @param ap
1239    */
1240   public void alignmentChanged(AlignmentViewPanel ap)
1241   {
1242     if (isPadGaps())
1243     {
1244       alignment.padGaps();
1245     }
1246     if (autoCalculateConsensus)
1247     {
1248       updateConsensus(ap);
1249     }
1250     if (hconsensus != null && autoCalculateConsensus)
1251     {
1252       updateConservation(ap);
1253     }
1254     if (autoCalculateStrucConsensus)
1255     {
1256       updateStrucConsensus(ap);
1257     }
1258
1259     // Reset endRes of groups if beyond alignment width
1260     int alWidth = alignment.getWidth();
1261     List<SequenceGroup> groups = alignment.getGroups();
1262     if (groups != null)
1263     {
1264       for (SequenceGroup sg : groups)
1265       {
1266         if (sg.getEndRes() > alWidth)
1267         {
1268           sg.setEndRes(alWidth - 1);
1269         }
1270       }
1271     }
1272
1273     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1274     {
1275       selectionGroup.setEndRes(alWidth - 1);
1276     }
1277
1278     resetAllColourSchemes();
1279     calculator.restartWorkers();
1280     // alignment.adjustSequenceAnnotations();
1281   }
1282
1283   /**
1284    * reset scope and do calculations for all applied colourschemes on alignment
1285    */
1286   void resetAllColourSchemes()
1287   {
1288     ColourSchemeI cs = globalColourScheme;
1289     if (cs != null)
1290     {
1291       cs.alignmentChanged(alignment, null);
1292
1293       cs.setConsensus(hconsensus);
1294       if (cs.conservationApplied())
1295       {
1296         cs.setConservation(Conservation.calculateConservation("All",
1297                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1298                 alignment.getWidth(), false, getConsPercGaps(), false));
1299       }
1300     }
1301
1302     for (SequenceGroup sg : alignment.getGroups())
1303     {
1304       if (sg.cs != null)
1305       {
1306         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1307       }
1308       sg.recalcConservation();
1309     }
1310   }
1311
1312   protected void initAutoAnnotation()
1313   {
1314     // TODO: add menu option action that nulls or creates consensus object
1315     // depending on if the user wants to see the annotation or not in a
1316     // specific alignment
1317
1318     if (hconsensus == null && !isDataset)
1319     {
1320       if (!alignment.isNucleotide())
1321       {
1322         initConservation();
1323         initQuality();
1324       }
1325       else
1326       {
1327         initRNAStructure();
1328       }
1329       initConsensus();
1330     }
1331   }
1332
1333   private void initConsensus()
1334   {
1335
1336     consensus = new AlignmentAnnotation("Consensus", "PID",
1337             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1338     consensus.hasText = true;
1339     consensus.autoCalculated = true;
1340
1341     if (showConsensus)
1342     {
1343       alignment.addAnnotation(consensus);
1344     }
1345   }
1346
1347   private void initConservation()
1348   {
1349     if (showConservation)
1350     {
1351       if (conservation == null)
1352       {
1353         conservation = new AlignmentAnnotation("Conservation",
1354                 "Conservation of total alignment less than "
1355                         + getConsPercGaps() + "% gaps",
1356                 new Annotation[1], 0f, 11f,
1357                 AlignmentAnnotation.BAR_GRAPH);
1358         conservation.hasText = true;
1359         conservation.autoCalculated = true;
1360         alignment.addAnnotation(conservation);
1361       }
1362     }
1363   }
1364   private void initQuality()
1365   {
1366     if (showQuality)
1367     {
1368       if (quality == null)
1369       {
1370         quality = new AlignmentAnnotation("Quality",
1371                 "Alignment Quality based on Blosum62 scores",
1372                 new Annotation[1], 0f, 11f,
1373                 AlignmentAnnotation.BAR_GRAPH);
1374         quality.hasText = true;
1375         quality.autoCalculated = true;
1376         alignment.addAnnotation(quality);
1377       }
1378     }
1379   }
1380   private void initRNAStructure()
1381   {
1382     if (alignment.hasRNAStructure() && strucConsensus==null)
1383     {
1384       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1385               new Annotation[1], 0f, 100f,
1386               AlignmentAnnotation.BAR_GRAPH);
1387       strucConsensus.hasText = true;
1388       strucConsensus.autoCalculated = true;
1389
1390       if (showConsensus)
1391       {
1392         alignment.addAnnotation(strucConsensus);
1393       }
1394     }
1395   }
1396   /*
1397    * (non-Javadoc)
1398    * 
1399    * @see jalview.api.AlignViewportI#calcPanelHeight()
1400    */
1401   public int calcPanelHeight()
1402   {
1403     // setHeight of panels
1404     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1405     int height = 0;
1406     int charHeight = getCharHeight();
1407     if (aa != null)
1408     {
1409       boolean graphgrp[] = null;
1410       for (int i = 0; i < aa.length; i++)
1411       {
1412         if (aa[i] == null)
1413         {
1414           System.err.println("Null annotation row: ignoring.");
1415           continue;
1416         }
1417         if (!aa[i].visible)
1418         {
1419           continue;
1420         }
1421         if (aa[i].graphGroup > -1)
1422         {
1423           if (graphgrp == null)
1424           {
1425             graphgrp = new boolean[aa.length];
1426           }
1427           if (graphgrp[aa[i].graphGroup])
1428           {
1429             continue;
1430           }
1431           else
1432           {
1433             graphgrp[aa[i].graphGroup] = true;
1434           }
1435         }
1436         aa[i].height = 0;
1437
1438         if (aa[i].hasText)
1439         {
1440           aa[i].height += charHeight;
1441         }
1442
1443         if (aa[i].hasIcons)
1444         {
1445           aa[i].height += 16;
1446         }
1447
1448         if (aa[i].graph > 0)
1449         {
1450           aa[i].height += aa[i].graphHeight;
1451         }
1452
1453         if (aa[i].height == 0)
1454         {
1455           aa[i].height = 20;
1456         }
1457
1458         height += aa[i].height;
1459       }
1460     }
1461     if (height == 0)
1462     {
1463       // set minimum
1464       height = 20;
1465     }
1466     return height;
1467   }
1468
1469   @Override
1470   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
1471           boolean preserveNewGroupSettings)
1472   {
1473     boolean updateCalcs = false;
1474     boolean conv = isShowGroupConservation();
1475     boolean cons = isShowGroupConsensus();
1476     boolean showprf = isShowSequenceLogo();
1477     boolean showConsHist = isShowConsensusHistogram();
1478     boolean normLogo = isNormaliseSequenceLogo();
1479
1480     /**
1481      * TODO reorder the annotation rows according to group/sequence ordering on
1482      * alignment
1483      */
1484     boolean sortg = true;
1485
1486     // remove old automatic annotation
1487     // add any new annotation
1488
1489     // intersect alignment annotation with alignment groups
1490
1491     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
1492     List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
1493     if (aan != null)
1494     {
1495       for (int an = 0; an < aan.length; an++)
1496       {
1497         if (aan[an].autoCalculated && aan[an].groupRef != null)
1498         {
1499           oldrfs.add(aan[an].groupRef);
1500           alignment.deleteAnnotation(aan[an]);
1501           aan[an] = null;
1502         }
1503       }
1504     }
1505     if (alignment.getGroups() != null)
1506     {
1507       for (SequenceGroup sg : alignment.getGroups())
1508       {
1509         updateCalcs = false;
1510         if (applyGlobalSettings
1511                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
1512         {
1513           // set defaults for this group's conservation/consensus
1514           sg.setshowSequenceLogo(showprf);
1515           sg.setShowConsensusHistogram(showConsHist);
1516           sg.setNormaliseSequenceLogo(normLogo);
1517         }
1518         if (conv)
1519         {
1520           updateCalcs = true;
1521           alignment.addAnnotation(sg.getConservationRow(), 0);
1522         }
1523         if (cons)
1524         {
1525           updateCalcs = true;
1526           alignment.addAnnotation(sg.getConsensus(), 0);
1527         }
1528         // refresh the annotation rows
1529         if (updateCalcs)
1530         {
1531           sg.recalcConservation();
1532         }
1533       }
1534     }
1535     oldrfs.clear();
1536   }
1537   /**
1538    * show the reference sequence in the alignment view
1539    */
1540   private boolean displayReferenceSeq=false;
1541   /**
1542    * colour according to the reference sequence defined on the alignment
1543    */
1544   private boolean colourByReferenceSeq=false;
1545
1546   public boolean isDisplayReferenceSeq()
1547   {
1548     return alignment.hasSeqrep() && displayReferenceSeq;
1549   }
1550
1551   public void setDisplayReferenceSeq(boolean displayReferenceSeq)
1552   {
1553     this.displayReferenceSeq = displayReferenceSeq;
1554   }
1555
1556   public boolean isColourByReferenceSeq()
1557   {
1558     return alignment.hasSeqrep() && colourByReferenceSeq;
1559   }
1560
1561   public void setColourByReferenceSeq(boolean colourByReferenceSeq)
1562   {
1563     this.colourByReferenceSeq = colourByReferenceSeq;
1564   }
1565
1566 }