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