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