JAL-1385 expose core view model methods in interface
[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   @Override
598   public SequenceGroup getSelectionGroup()
599   {
600     return selectionGroup;
601   }
602
603   /**
604    * Set the selection group for this window.
605    * 
606    * @param sg
607    *          - group holding references to sequences in this alignment view
608    * 
609    */
610   @Override
611   public void setSelectionGroup(SequenceGroup sg)
612   {
613     selectionGroup = sg;
614   }
615
616   public void setHiddenColumns(ColumnSelection colsel)
617   {
618     this.colSel = colsel;
619     if (colSel.getHiddenColumns() != null)
620     {
621       hasHiddenColumns = true;
622     }
623   }
624
625   @Override
626   public ColumnSelection getColumnSelection()
627   {
628     return colSel;
629   }
630
631   public void setColumnSelection(ColumnSelection colSel)
632   {
633     this.colSel = colSel;
634   }
635
636   /**
637    * 
638    * @return
639    */
640   @Override
641   public Map<SequenceI, SequenceCollectionI> getHiddenRepSequences()
642   {
643     return hiddenRepSequences;
644   }
645
646   @Override
647   public void setHiddenRepSequences(
648           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
649   {
650     this.hiddenRepSequences = hiddenRepSequences;
651   }
652
653   protected boolean hasHiddenColumns = false;
654
655   public void updateHiddenColumns()
656   {
657     hasHiddenColumns = colSel.getHiddenColumns() != null;
658   }
659
660   protected boolean hasHiddenRows = false;
661
662   public boolean hasHiddenRows()
663   {
664     return hasHiddenRows;
665   }
666
667   protected SequenceGroup selectionGroup;
668
669   public void setSequenceSetId(String newid)
670   {
671     if (sequenceSetID != null)
672     {
673       System.err
674               .println("Warning - overwriting a sequenceSetId for a viewport!");
675     }
676     sequenceSetID = new String(newid);
677   }
678
679   public String getSequenceSetId()
680   {
681     if (sequenceSetID == null)
682     {
683       sequenceSetID = alignment.hashCode() + "";
684     }
685
686     return sequenceSetID;
687   }
688
689   /**
690    * unique viewId for synchronizing state (e.g. with stored Jalview Project)
691    * 
692    */
693   protected String viewId = null;
694
695   public String getViewId()
696   {
697     if (viewId == null)
698     {
699       viewId = this.getSequenceSetId() + "." + this.hashCode() + "";
700     }
701     return viewId;
702   }
703
704   public void setIgnoreGapsConsensus(boolean b, AlignmentViewPanel ap)
705   {
706     ignoreGapsInConsensusCalculation = b;
707     if (ap != null)
708     {
709       updateConsensus(ap);
710       if (globalColourScheme != null)
711       {
712         globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
713                 ignoreGapsInConsensusCalculation);
714       }
715     }
716
717   }
718
719   private long sgrouphash = -1, colselhash = -1;
720
721   /**
722    * checks current SelectionGroup against record of last hash value, and
723    * updates record.
724    * 
725    * @param b
726    *          update the record of last hash value
727    * 
728    * @return true if SelectionGroup changed since last call (when b is true)
729    */
730   public boolean isSelectionGroupChanged(boolean b)
731   {
732     int hc = (selectionGroup == null || selectionGroup.getSize() == 0) ? -1
733             : selectionGroup.hashCode();
734     if (hc != -1 && hc != sgrouphash)
735     {
736       if (b)
737       {
738         sgrouphash = hc;
739       }
740       return true;
741     }
742     return false;
743   }
744
745   /**
746    * checks current colsel against record of last hash value, and optionally
747    * updates record.
748    * 
749    * @param b
750    *          update the record of last hash value
751    * @return true if colsel changed since last call (when b is true)
752    */
753   public boolean isColSelChanged(boolean b)
754   {
755     int hc = (colSel == null || colSel.size() == 0) ? -1 : colSel
756             .hashCode();
757     if (hc != -1 && hc != colselhash)
758     {
759       if (b)
760       {
761         colselhash = hc;
762       }
763       return true;
764     }
765     return false;
766   }
767
768   @Override
769   public boolean getIgnoreGapsConsensus()
770   {
771     return ignoreGapsInConsensusCalculation;
772   }
773
774   // / property change stuff
775
776   // JBPNote Prolly only need this in the applet version.
777   private final java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
778           this);
779
780   protected boolean showConservation = true;
781
782   protected boolean showQuality = true;
783
784   protected boolean showConsensus = true;
785
786   /**
787    * Property change listener for changes in alignment
788    * 
789    * @param listener
790    *          DOCUMENT ME!
791    */
792   public void addPropertyChangeListener(
793           java.beans.PropertyChangeListener listener)
794   {
795     changeSupport.addPropertyChangeListener(listener);
796   }
797
798   /**
799    * DOCUMENT ME!
800    * 
801    * @param listener
802    *          DOCUMENT ME!
803    */
804   public void removePropertyChangeListener(
805           java.beans.PropertyChangeListener listener)
806   {
807     changeSupport.removePropertyChangeListener(listener);
808   }
809
810   /**
811    * Property change listener for changes in alignment
812    * 
813    * @param prop
814    *          DOCUMENT ME!
815    * @param oldvalue
816    *          DOCUMENT ME!
817    * @param newvalue
818    *          DOCUMENT ME!
819    */
820   public void firePropertyChange(String prop, Object oldvalue,
821           Object newvalue)
822   {
823     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
824   }
825
826   // common hide/show column stuff
827
828   public void hideSelectedColumns()
829   {
830     if (colSel.size() < 1)
831     {
832       return;
833     }
834
835     colSel.hideSelectedColumns();
836     setSelectionGroup(null);
837
838     hasHiddenColumns = true;
839   }
840
841   public void hideColumns(int start, int end)
842   {
843     if (start == end)
844     {
845       colSel.hideColumns(start);
846     }
847     else
848     {
849       colSel.hideColumns(start, end);
850     }
851
852     hasHiddenColumns = true;
853   }
854
855   public void showColumn(int col)
856   {
857     colSel.revealHiddenColumns(col);
858     if (colSel.getHiddenColumns() == null)
859     {
860       hasHiddenColumns = false;
861     }
862   }
863
864   public void showAllHiddenColumns()
865   {
866     colSel.revealAllHiddenColumns();
867     hasHiddenColumns = false;
868   }
869
870   // common hide/show seq stuff
871   public void showAllHiddenSeqs()
872   {
873     if (alignment.getHiddenSequences().getSize() > 0)
874     {
875       if (selectionGroup == null)
876       {
877         selectionGroup = new SequenceGroup();
878         selectionGroup.setEndRes(alignment.getWidth() - 1);
879       }
880       Vector tmp = alignment.getHiddenSequences().showAll(
881               hiddenRepSequences);
882       for (int t = 0; t < tmp.size(); t++)
883       {
884         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
885       }
886
887       hasHiddenRows = false;
888       hiddenRepSequences = null;
889
890       firePropertyChange("alignment", null, alignment.getSequences());
891       // used to set hasHiddenRows/hiddenRepSequences here, after the property
892       // changed event
893       sendSelection();
894     }
895   }
896
897   public void showSequence(int index)
898   {
899     Vector tmp = alignment.getHiddenSequences().showSequence(index,
900             hiddenRepSequences);
901     if (tmp.size() > 0)
902     {
903       if (selectionGroup == null)
904       {
905         selectionGroup = new SequenceGroup();
906         selectionGroup.setEndRes(alignment.getWidth() - 1);
907       }
908
909       for (int t = 0; t < tmp.size(); t++)
910       {
911         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
912       }
913       // JBPNote: refactor: only update flag if we modified visiblity (used to
914       // do this regardless)
915       if (alignment.getHiddenSequences().getSize() < 1)
916       {
917         hasHiddenRows = false;
918       }
919       firePropertyChange("alignment", null, alignment.getSequences());
920       sendSelection();
921     }
922   }
923
924   public void hideAllSelectedSeqs()
925   {
926     if (selectionGroup == null || selectionGroup.getSize() < 1)
927     {
928       return;
929     }
930
931     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
932
933     hideSequence(seqs);
934
935     setSelectionGroup(null);
936   }
937
938   public void hideSequence(SequenceI[] seq)
939   {
940     if (seq != null)
941     {
942       for (int i = 0; i < seq.length; i++)
943       {
944         alignment.getHiddenSequences().hideSequence(seq[i]);
945       }
946       hasHiddenRows = true;
947       firePropertyChange("alignment", null, alignment.getSequences());
948     }
949   }
950
951   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
952   {
953     int sSize = sg.getSize();
954     if (sSize < 2)
955     {
956       return;
957     }
958
959     if (hiddenRepSequences == null)
960     {
961       hiddenRepSequences = new Hashtable();
962     }
963
964     hiddenRepSequences.put(repSequence, sg);
965
966     // Hide all sequences except the repSequence
967     SequenceI[] seqs = new SequenceI[sSize - 1];
968     int index = 0;
969     for (int i = 0; i < sSize; i++)
970     {
971       if (sg.getSequenceAt(i) != repSequence)
972       {
973         if (index == sSize - 1)
974         {
975           return;
976         }
977
978         seqs[index++] = sg.getSequenceAt(i);
979       }
980     }
981     sg.setSeqrep(repSequence); // note: not done in 2.7applet
982     sg.setHidereps(true); // note: not done in 2.7applet
983     hideSequence(seqs);
984
985   }
986
987   public boolean isHiddenRepSequence(SequenceI seq)
988   {
989     return hiddenRepSequences != null
990             && hiddenRepSequences.containsKey(seq);
991   }
992
993   public SequenceGroup getRepresentedSequences(SequenceI seq)
994   {
995     return (SequenceGroup) (hiddenRepSequences == null ? null
996             : hiddenRepSequences.get(seq));
997   }
998
999   public int adjustForHiddenSeqs(int alignmentIndex)
1000   {
1001     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1002             alignmentIndex);
1003   }
1004
1005   // Selection manipulation
1006   /**
1007    * broadcast selection to any interested parties
1008    */
1009   public abstract void sendSelection();
1010
1011   public void invertColumnSelection()
1012   {
1013     colSel.invertColumnSelection(0, alignment.getWidth());
1014   }
1015
1016   /**
1017    * This method returns an array of new SequenceI objects derived from the
1018    * whole alignment or just the current selection with start and end points
1019    * adjusted
1020    * 
1021    * @note if you need references to the actual SequenceI objects in the
1022    *       alignment or currently selected then use getSequenceSelection()
1023    * @return selection as new sequenceI objects
1024    */
1025   public SequenceI[] getSelectionAsNewSequence()
1026   {
1027     SequenceI[] sequences;
1028     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1029     // this was the only caller in the applet for this method
1030     // JBPNote: in applet, this method returned references to the alignment
1031     // sequences, and it did not honour the presence/absence of annotation
1032     // attached to the alignment (probably!)
1033     if (selectionGroup == null || selectionGroup.getSize() == 0)
1034     {
1035       sequences = alignment.getSequencesArray();
1036       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1037       for (int i = 0; i < sequences.length; i++)
1038       {
1039         sequences[i] = new Sequence(sequences[i], annots); // construct new
1040         // sequence with
1041         // subset of visible
1042         // annotation
1043       }
1044     }
1045     else
1046     {
1047       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1048     }
1049
1050     return sequences;
1051   }
1052
1053   /**
1054    * get the currently selected sequence objects or all the sequences in the
1055    * alignment.
1056    * 
1057    * @return array of references to sequence objects
1058    */
1059   @Override
1060   public SequenceI[] getSequenceSelection()
1061   {
1062     SequenceI[] sequences = null;
1063     if (selectionGroup != null)
1064     {
1065       sequences = selectionGroup.getSequencesInOrder(alignment);
1066     }
1067     if (sequences == null)
1068     {
1069       sequences = alignment.getSequencesArray();
1070     }
1071     return sequences;
1072   }
1073
1074   /**
1075    * This method returns the visible alignment as text, as seen on the GUI, ie
1076    * if columns are hidden they will not be returned in the result. Use this for
1077    * calculating trees, PCA, redundancy etc on views which contain hidden
1078    * columns.
1079    * 
1080    * @return String[]
1081    */
1082   @Override
1083   public jalview.datamodel.CigarArray getViewAsCigars(
1084           boolean selectedRegionOnly)
1085   {
1086     return new jalview.datamodel.CigarArray(alignment,
1087             (hasHiddenColumns ? colSel : null),
1088             (selectedRegionOnly ? selectionGroup : null));
1089   }
1090
1091   /**
1092    * return a compact representation of the current alignment selection to pass
1093    * to an analysis function
1094    * 
1095    * @param selectedOnly
1096    *          boolean true to just return the selected view
1097    * @return AlignmentView
1098    */
1099   @Override
1100   public jalview.datamodel.AlignmentView getAlignmentView(
1101           boolean selectedOnly)
1102   {
1103     return getAlignmentView(selectedOnly, false);
1104   }
1105
1106   /**
1107    * return a compact representation of the current alignment selection to pass
1108    * to an analysis function
1109    * 
1110    * @param selectedOnly
1111    *          boolean true to just return the selected view
1112    * @param markGroups
1113    *          boolean true to annotate the alignment view with groups on the
1114    *          alignment (and intersecting with selected region if selectedOnly
1115    *          is true)
1116    * @return AlignmentView
1117    */
1118   @Override
1119   public jalview.datamodel.AlignmentView getAlignmentView(
1120           boolean selectedOnly, boolean markGroups)
1121   {
1122     return new AlignmentView(alignment, colSel, selectionGroup,
1123             hasHiddenColumns, selectedOnly, markGroups);
1124   }
1125
1126   /**
1127    * This method returns the visible alignment as text, as seen on the GUI, ie
1128    * if columns are hidden they will not be returned in the result. Use this for
1129    * calculating trees, PCA, redundancy etc on views which contain hidden
1130    * columns.
1131    * 
1132    * @return String[]
1133    */
1134   @Override
1135   public String[] getViewAsString(boolean selectedRegionOnly)
1136   {
1137     String[] selection = null;
1138     SequenceI[] seqs = null;
1139     int i, iSize;
1140     int start = 0, end = 0;
1141     if (selectedRegionOnly && selectionGroup != null)
1142     {
1143       iSize = selectionGroup.getSize();
1144       seqs = selectionGroup.getSequencesInOrder(alignment);
1145       start = selectionGroup.getStartRes();
1146       end = selectionGroup.getEndRes() + 1;
1147     }
1148     else
1149     {
1150       iSize = alignment.getHeight();
1151       seqs = alignment.getSequencesArray();
1152       end = alignment.getWidth();
1153     }
1154
1155     selection = new String[iSize];
1156     if (hasHiddenColumns)
1157     {
1158       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1159     }
1160     else
1161     {
1162       for (i = 0; i < iSize; i++)
1163       {
1164         selection[i] = seqs[i].getSequenceAsString(start, end);
1165       }
1166
1167     }
1168     return selection;
1169   }
1170
1171   /**
1172    * return visible region boundaries within given column range
1173    * 
1174    * @param min
1175    *          first column (inclusive, from 0)
1176    * @param max
1177    *          last column (exclusive)
1178    * @return int[][] range of {start,end} visible positions
1179    */
1180   public int[][] getVisibleRegionBoundaries(int min, int max)
1181   {
1182     Vector regions = new Vector();
1183     int start = min;
1184     int end = max;
1185
1186     do
1187     {
1188       if (hasHiddenColumns)
1189       {
1190         if (start == 0)
1191         {
1192           start = colSel.adjustForHiddenColumns(start);
1193         }
1194
1195         end = colSel.getHiddenBoundaryRight(start);
1196         if (start == end)
1197         {
1198           end = max;
1199         }
1200         if (end > max)
1201         {
1202           end = max;
1203         }
1204       }
1205
1206       regions.addElement(new int[]
1207       { start, end });
1208
1209       if (hasHiddenColumns)
1210       {
1211         start = colSel.adjustForHiddenColumns(end);
1212         start = colSel.getHiddenBoundaryLeft(start) + 1;
1213       }
1214     } while (end < max);
1215
1216     int[][] startEnd = new int[regions.size()][2];
1217
1218     regions.copyInto(startEnd);
1219
1220     return startEnd;
1221
1222   }
1223
1224   /**
1225    * @return the padGaps
1226    */
1227   public boolean isPadGaps()
1228   {
1229     return padGaps;
1230   }
1231
1232   /**
1233    * @param padGaps
1234    *          the padGaps to set
1235    */
1236   public void setPadGaps(boolean padGaps)
1237   {
1238     this.padGaps = padGaps;
1239   }
1240
1241   /**
1242    * apply any post-edit constraints and trigger any calculations needed after
1243    * an edit has been performed on the alignment
1244    * 
1245    * @param ap
1246    */
1247   public void alignmentChanged(AlignmentViewPanel ap)
1248   {
1249     if (isPadGaps())
1250     {
1251       alignment.padGaps();
1252     }
1253     if (autoCalculateConsensus)
1254     {
1255       updateConsensus(ap);
1256     }
1257     if (hconsensus != null && autoCalculateConsensus)
1258     {
1259       updateConservation(ap);
1260     }
1261     if (autoCalculateStrucConsensus)
1262     {
1263       updateStrucConsensus(ap);
1264     }
1265
1266     // Reset endRes of groups if beyond alignment width
1267     int alWidth = alignment.getWidth();
1268     List<SequenceGroup> groups = alignment.getGroups();
1269     if (groups != null)
1270     {
1271       for (SequenceGroup sg : groups)
1272       {
1273         if (sg.getEndRes() > alWidth)
1274         {
1275           sg.setEndRes(alWidth - 1);
1276         }
1277       }
1278     }
1279
1280     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1281     {
1282       selectionGroup.setEndRes(alWidth - 1);
1283     }
1284
1285     resetAllColourSchemes();
1286     calculator.restartWorkers();
1287     // alignment.adjustSequenceAnnotations();
1288   }
1289
1290   /**
1291    * reset scope and do calculations for all applied colourschemes on alignment
1292    */
1293   void resetAllColourSchemes()
1294   {
1295     ColourSchemeI cs = globalColourScheme;
1296     if (cs != null)
1297     {
1298       cs.alignmentChanged(alignment, null);
1299
1300       cs.setConsensus(hconsensus);
1301       if (cs.conservationApplied())
1302       {
1303         cs.setConservation(Conservation.calculateConservation("All",
1304                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1305                 alignment.getWidth(), false, getConsPercGaps(), false));
1306       }
1307     }
1308
1309     for (SequenceGroup sg : alignment.getGroups())
1310     {
1311       if (sg.cs != null)
1312       {
1313         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1314       }
1315       sg.recalcConservation();
1316     }
1317   }
1318
1319   protected void initAutoAnnotation()
1320   {
1321     // TODO: add menu option action that nulls or creates consensus object
1322     // depending on if the user wants to see the annotation or not in a
1323     // specific alignment
1324
1325     if (hconsensus == null && !isDataset)
1326     {
1327       if (!alignment.isNucleotide())
1328       {
1329         initConservation();
1330         initQuality();
1331       }
1332       else
1333       {
1334         initRNAStructure();
1335       }
1336       initConsensus();
1337     }
1338   }
1339
1340   private void initConsensus()
1341   {
1342
1343     consensus = new AlignmentAnnotation("Consensus", "PID",
1344             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1345     consensus.hasText = true;
1346     consensus.autoCalculated = true;
1347
1348     if (showConsensus)
1349     {
1350       alignment.addAnnotation(consensus);
1351     }
1352   }
1353
1354   private void initConservation()
1355   {
1356     if (showConservation)
1357     {
1358       if (conservation == null)
1359       {
1360         conservation = new AlignmentAnnotation("Conservation",
1361                 "Conservation of total alignment less than "
1362                         + getConsPercGaps() + "% gaps",
1363                 new Annotation[1], 0f, 11f,
1364                 AlignmentAnnotation.BAR_GRAPH);
1365         conservation.hasText = true;
1366         conservation.autoCalculated = true;
1367         alignment.addAnnotation(conservation);
1368       }
1369     }
1370   }
1371   private void initQuality()
1372   {
1373     if (showQuality)
1374     {
1375       if (quality == null)
1376       {
1377         quality = new AlignmentAnnotation("Quality",
1378                 "Alignment Quality based on Blosum62 scores",
1379                 new Annotation[1], 0f, 11f,
1380                 AlignmentAnnotation.BAR_GRAPH);
1381         quality.hasText = true;
1382         quality.autoCalculated = true;
1383         alignment.addAnnotation(quality);
1384       }
1385     }
1386   }
1387   private void initRNAStructure()
1388   {
1389     if (alignment.hasRNAStructure() && strucConsensus==null)
1390     {
1391       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1392               new Annotation[1], 0f, 100f,
1393               AlignmentAnnotation.BAR_GRAPH);
1394       strucConsensus.hasText = true;
1395       strucConsensus.autoCalculated = true;
1396
1397       if (showConsensus)
1398       {
1399         alignment.addAnnotation(strucConsensus);
1400       }
1401     }
1402   }
1403   /*
1404    * (non-Javadoc)
1405    * 
1406    * @see jalview.api.AlignViewportI#calcPanelHeight()
1407    */
1408   public int calcPanelHeight()
1409   {
1410     // setHeight of panels
1411     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1412     int height = 0;
1413     int charHeight = getCharHeight();
1414     if (aa != null)
1415     {
1416       boolean graphgrp[] = null;
1417       for (int i = 0; i < aa.length; i++)
1418       {
1419         if (aa[i] == null)
1420         {
1421           System.err.println("Null annotation row: ignoring.");
1422           continue;
1423         }
1424         if (!aa[i].visible)
1425         {
1426           continue;
1427         }
1428         if (aa[i].graphGroup > -1)
1429         {
1430           if (graphgrp == null)
1431           {
1432             graphgrp = new boolean[aa.length];
1433           }
1434           if (graphgrp[aa[i].graphGroup])
1435           {
1436             continue;
1437           }
1438           else
1439           {
1440             graphgrp[aa[i].graphGroup] = true;
1441           }
1442         }
1443         aa[i].height = 0;
1444
1445         if (aa[i].hasText)
1446         {
1447           aa[i].height += charHeight;
1448         }
1449
1450         if (aa[i].hasIcons)
1451         {
1452           aa[i].height += 16;
1453         }
1454
1455         if (aa[i].graph > 0)
1456         {
1457           aa[i].height += aa[i].graphHeight;
1458         }
1459
1460         if (aa[i].height == 0)
1461         {
1462           aa[i].height = 20;
1463         }
1464
1465         height += aa[i].height;
1466       }
1467     }
1468     if (height == 0)
1469     {
1470       // set minimum
1471       height = 20;
1472     }
1473     return height;
1474   }
1475
1476   @Override
1477   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
1478           boolean preserveNewGroupSettings)
1479   {
1480     boolean updateCalcs = false;
1481     boolean conv = isShowGroupConservation();
1482     boolean cons = isShowGroupConsensus();
1483     boolean showprf = isShowSequenceLogo();
1484     boolean showConsHist = isShowConsensusHistogram();
1485     boolean normLogo = isNormaliseSequenceLogo();
1486
1487     /**
1488      * TODO reorder the annotation rows according to group/sequence ordering on
1489      * alignment
1490      */
1491     boolean sortg = true;
1492
1493     // remove old automatic annotation
1494     // add any new annotation
1495
1496     // intersect alignment annotation with alignment groups
1497
1498     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
1499     List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
1500     if (aan != null)
1501     {
1502       for (int an = 0; an < aan.length; an++)
1503       {
1504         if (aan[an].autoCalculated && aan[an].groupRef != null)
1505         {
1506           oldrfs.add(aan[an].groupRef);
1507           alignment.deleteAnnotation(aan[an]);
1508           aan[an] = null;
1509         }
1510       }
1511     }
1512     if (alignment.getGroups() != null)
1513     {
1514       for (SequenceGroup sg : alignment.getGroups())
1515       {
1516         updateCalcs = false;
1517         if (applyGlobalSettings
1518                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
1519         {
1520           // set defaults for this group's conservation/consensus
1521           sg.setshowSequenceLogo(showprf);
1522           sg.setShowConsensusHistogram(showConsHist);
1523           sg.setNormaliseSequenceLogo(normLogo);
1524         }
1525         if (conv)
1526         {
1527           updateCalcs = true;
1528           alignment.addAnnotation(sg.getConservationRow(), 0);
1529         }
1530         if (cons)
1531         {
1532           updateCalcs = true;
1533           alignment.addAnnotation(sg.getConsensus(), 0);
1534         }
1535         // refresh the annotation rows
1536         if (updateCalcs)
1537         {
1538           sg.recalcConservation();
1539         }
1540       }
1541     }
1542     oldrfs.clear();
1543   }
1544
1545 }