JAL-1164 regression on JAL-1292 use bitset to mark graphGroup as rendered
[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.awt.Color;
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   Hashtable sequenceColours;
789
790   /**
791    * Property change listener for changes in alignment
792    * 
793    * @param listener
794    *          DOCUMENT ME!
795    */
796   public void addPropertyChangeListener(
797           java.beans.PropertyChangeListener listener)
798   {
799     changeSupport.addPropertyChangeListener(listener);
800   }
801
802   /**
803    * DOCUMENT ME!
804    * 
805    * @param listener
806    *          DOCUMENT ME!
807    */
808   public void removePropertyChangeListener(
809           java.beans.PropertyChangeListener listener)
810   {
811     changeSupport.removePropertyChangeListener(listener);
812   }
813
814   /**
815    * Property change listener for changes in alignment
816    * 
817    * @param prop
818    *          DOCUMENT ME!
819    * @param oldvalue
820    *          DOCUMENT ME!
821    * @param newvalue
822    *          DOCUMENT ME!
823    */
824   public void firePropertyChange(String prop, Object oldvalue,
825           Object newvalue)
826   {
827     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
828   }
829
830   // common hide/show column stuff
831
832   public void hideSelectedColumns()
833   {
834     if (colSel.size() < 1)
835     {
836       return;
837     }
838
839     colSel.hideSelectedColumns();
840     setSelectionGroup(null);
841
842     hasHiddenColumns = true;
843   }
844
845   public void hideColumns(int start, int end)
846   {
847     if (start == end)
848     {
849       colSel.hideColumns(start);
850     }
851     else
852     {
853       colSel.hideColumns(start, end);
854     }
855
856     hasHiddenColumns = true;
857   }
858
859   public void showColumn(int col)
860   {
861     colSel.revealHiddenColumns(col);
862     if (colSel.getHiddenColumns() == null)
863     {
864       hasHiddenColumns = false;
865     }
866   }
867
868   public void showAllHiddenColumns()
869   {
870     colSel.revealAllHiddenColumns();
871     hasHiddenColumns = false;
872   }
873
874   // common hide/show seq stuff
875   public void showAllHiddenSeqs()
876   {
877     if (alignment.getHiddenSequences().getSize() > 0)
878     {
879       if (selectionGroup == null)
880       {
881         selectionGroup = new SequenceGroup();
882         selectionGroup.setEndRes(alignment.getWidth() - 1);
883       }
884       Vector tmp = alignment.getHiddenSequences().showAll(
885               hiddenRepSequences);
886       for (int t = 0; t < tmp.size(); t++)
887       {
888         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
889       }
890
891       hasHiddenRows = false;
892       hiddenRepSequences = null;
893
894       firePropertyChange("alignment", null, alignment.getSequences());
895       // used to set hasHiddenRows/hiddenRepSequences here, after the property
896       // changed event
897       sendSelection();
898     }
899   }
900
901   public void showSequence(int index)
902   {
903     Vector tmp = alignment.getHiddenSequences().showSequence(index,
904             hiddenRepSequences);
905     if (tmp.size() > 0)
906     {
907       if (selectionGroup == null)
908       {
909         selectionGroup = new SequenceGroup();
910         selectionGroup.setEndRes(alignment.getWidth() - 1);
911       }
912
913       for (int t = 0; t < tmp.size(); t++)
914       {
915         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
916       }
917       // JBPNote: refactor: only update flag if we modified visiblity (used to
918       // do this regardless)
919       if (alignment.getHiddenSequences().getSize() < 1)
920       {
921         hasHiddenRows = false;
922       }
923       firePropertyChange("alignment", null, alignment.getSequences());
924       sendSelection();
925     }
926   }
927
928   public void hideAllSelectedSeqs()
929   {
930     if (selectionGroup == null || selectionGroup.getSize() < 1)
931     {
932       return;
933     }
934
935     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
936
937     hideSequence(seqs);
938
939     setSelectionGroup(null);
940   }
941
942   public void hideSequence(SequenceI[] seq)
943   {
944     if (seq != null)
945     {
946       for (int i = 0; i < seq.length; i++)
947       {
948         alignment.getHiddenSequences().hideSequence(seq[i]);
949       }
950       hasHiddenRows = true;
951       firePropertyChange("alignment", null, alignment.getSequences());
952     }
953   }
954
955   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
956   {
957     int sSize = sg.getSize();
958     if (sSize < 2)
959     {
960       return;
961     }
962
963     if (hiddenRepSequences == null)
964     {
965       hiddenRepSequences = new Hashtable();
966     }
967
968     hiddenRepSequences.put(repSequence, sg);
969
970     // Hide all sequences except the repSequence
971     SequenceI[] seqs = new SequenceI[sSize - 1];
972     int index = 0;
973     for (int i = 0; i < sSize; i++)
974     {
975       if (sg.getSequenceAt(i) != repSequence)
976       {
977         if (index == sSize - 1)
978         {
979           return;
980         }
981
982         seqs[index++] = sg.getSequenceAt(i);
983       }
984     }
985     sg.setSeqrep(repSequence); // note: not done in 2.7applet
986     sg.setHidereps(true); // note: not done in 2.7applet
987     hideSequence(seqs);
988
989   }
990
991   public boolean isHiddenRepSequence(SequenceI seq)
992   {
993     return hiddenRepSequences != null
994             && hiddenRepSequences.containsKey(seq);
995   }
996
997   public SequenceGroup getRepresentedSequences(SequenceI seq)
998   {
999     return (SequenceGroup) (hiddenRepSequences == null ? null
1000             : hiddenRepSequences.get(seq));
1001   }
1002
1003   public int adjustForHiddenSeqs(int alignmentIndex)
1004   {
1005     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1006             alignmentIndex);
1007   }
1008
1009   // Selection manipulation
1010   /**
1011    * broadcast selection to any interested parties
1012    */
1013   public abstract void sendSelection();
1014
1015   public void invertColumnSelection()
1016   {
1017     colSel.invertColumnSelection(0, alignment.getWidth());
1018   }
1019
1020   /**
1021    * This method returns an array of new SequenceI objects derived from the
1022    * whole alignment or just the current selection with start and end points
1023    * adjusted
1024    * 
1025    * @note if you need references to the actual SequenceI objects in the
1026    *       alignment or currently selected then use getSequenceSelection()
1027    * @return selection as new sequenceI objects
1028    */
1029   public SequenceI[] getSelectionAsNewSequence()
1030   {
1031     SequenceI[] sequences;
1032     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1033     // this was the only caller in the applet for this method
1034     // JBPNote: in applet, this method returned references to the alignment
1035     // sequences, and it did not honour the presence/absence of annotation
1036     // attached to the alignment (probably!)
1037     if (selectionGroup == null || selectionGroup.getSize() == 0)
1038     {
1039       sequences = alignment.getSequencesArray();
1040       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1041       for (int i = 0; i < sequences.length; i++)
1042       {
1043         sequences[i] = new Sequence(sequences[i], annots); // construct new
1044         // sequence with
1045         // subset of visible
1046         // annotation
1047       }
1048     }
1049     else
1050     {
1051       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1052     }
1053
1054     return sequences;
1055   }
1056
1057   /**
1058    * get the currently selected sequence objects or all the sequences in the
1059    * alignment.
1060    * 
1061    * @return array of references to sequence objects
1062    */
1063   @Override
1064   public SequenceI[] getSequenceSelection()
1065   {
1066     SequenceI[] sequences = null;
1067     if (selectionGroup != null)
1068     {
1069       sequences = selectionGroup.getSequencesInOrder(alignment);
1070     }
1071     if (sequences == null)
1072     {
1073       sequences = alignment.getSequencesArray();
1074     }
1075     return sequences;
1076   }
1077
1078   /**
1079    * This method returns the visible alignment as text, as seen on the GUI, ie
1080    * if columns are hidden they will not be returned in the result. Use this for
1081    * calculating trees, PCA, redundancy etc on views which contain hidden
1082    * columns.
1083    * 
1084    * @return String[]
1085    */
1086   @Override
1087   public jalview.datamodel.CigarArray getViewAsCigars(
1088           boolean selectedRegionOnly)
1089   {
1090     return new jalview.datamodel.CigarArray(alignment,
1091             (hasHiddenColumns ? colSel : null),
1092             (selectedRegionOnly ? selectionGroup : null));
1093   }
1094
1095   /**
1096    * return a compact representation of the current alignment selection to pass
1097    * to an analysis function
1098    * 
1099    * @param selectedOnly
1100    *          boolean true to just return the selected view
1101    * @return AlignmentView
1102    */
1103   @Override
1104   public jalview.datamodel.AlignmentView getAlignmentView(
1105           boolean selectedOnly)
1106   {
1107     return getAlignmentView(selectedOnly, false);
1108   }
1109
1110   /**
1111    * return a compact representation of the current alignment selection to pass
1112    * to an analysis function
1113    * 
1114    * @param selectedOnly
1115    *          boolean true to just return the selected view
1116    * @param markGroups
1117    *          boolean true to annotate the alignment view with groups on the
1118    *          alignment (and intersecting with selected region if selectedOnly
1119    *          is true)
1120    * @return AlignmentView
1121    */
1122   @Override
1123   public jalview.datamodel.AlignmentView getAlignmentView(
1124           boolean selectedOnly, boolean markGroups)
1125   {
1126     return new AlignmentView(alignment, colSel, selectionGroup,
1127             hasHiddenColumns, selectedOnly, markGroups);
1128   }
1129
1130   /**
1131    * This method returns the visible alignment as text, as seen on the GUI, ie
1132    * if columns are hidden they will not be returned in the result. Use this for
1133    * calculating trees, PCA, redundancy etc on views which contain hidden
1134    * columns.
1135    * 
1136    * @return String[]
1137    */
1138   @Override
1139   public String[] getViewAsString(boolean selectedRegionOnly)
1140   {
1141     String[] selection = null;
1142     SequenceI[] seqs = null;
1143     int i, iSize;
1144     int start = 0, end = 0;
1145     if (selectedRegionOnly && selectionGroup != null)
1146     {
1147       iSize = selectionGroup.getSize();
1148       seqs = selectionGroup.getSequencesInOrder(alignment);
1149       start = selectionGroup.getStartRes();
1150       end = selectionGroup.getEndRes() + 1;
1151     }
1152     else
1153     {
1154       iSize = alignment.getHeight();
1155       seqs = alignment.getSequencesArray();
1156       end = alignment.getWidth();
1157     }
1158
1159     selection = new String[iSize];
1160     if (hasHiddenColumns)
1161     {
1162       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1163     }
1164     else
1165     {
1166       for (i = 0; i < iSize; i++)
1167       {
1168         selection[i] = seqs[i].getSequenceAsString(start, end);
1169       }
1170
1171     }
1172     return selection;
1173   }
1174
1175   /**
1176    * return visible region boundaries within given column range
1177    * 
1178    * @param min
1179    *          first column (inclusive, from 0)
1180    * @param max
1181    *          last column (exclusive)
1182    * @return int[][] range of {start,end} visible positions
1183    */
1184   public int[][] getVisibleRegionBoundaries(int min, int max)
1185   {
1186     Vector regions = new Vector();
1187     int start = min;
1188     int end = max;
1189
1190     do
1191     {
1192       if (hasHiddenColumns)
1193       {
1194         if (start == 0)
1195         {
1196           start = colSel.adjustForHiddenColumns(start);
1197         }
1198
1199         end = colSel.getHiddenBoundaryRight(start);
1200         if (start == end)
1201         {
1202           end = max;
1203         }
1204         if (end > max)
1205         {
1206           end = max;
1207         }
1208       }
1209
1210       regions.addElement(new int[]
1211       { start, end });
1212
1213       if (hasHiddenColumns)
1214       {
1215         start = colSel.adjustForHiddenColumns(end);
1216         start = colSel.getHiddenBoundaryLeft(start) + 1;
1217       }
1218     } while (end < max);
1219
1220     int[][] startEnd = new int[regions.size()][2];
1221
1222     regions.copyInto(startEnd);
1223
1224     return startEnd;
1225
1226   }
1227
1228   /**
1229    * @return the padGaps
1230    */
1231   public boolean isPadGaps()
1232   {
1233     return padGaps;
1234   }
1235
1236   /**
1237    * @param padGaps
1238    *          the padGaps to set
1239    */
1240   public void setPadGaps(boolean padGaps)
1241   {
1242     this.padGaps = padGaps;
1243   }
1244
1245   /**
1246    * apply any post-edit constraints and trigger any calculations needed after
1247    * an edit has been performed on the alignment
1248    * 
1249    * @param ap
1250    */
1251   public void alignmentChanged(AlignmentViewPanel ap)
1252   {
1253     if (isPadGaps())
1254     {
1255       alignment.padGaps();
1256     }
1257     if (autoCalculateConsensus)
1258     {
1259       updateConsensus(ap);
1260     }
1261     if (hconsensus != null && autoCalculateConsensus)
1262     {
1263       updateConservation(ap);
1264     }
1265     if (autoCalculateStrucConsensus)
1266     {
1267       updateStrucConsensus(ap);
1268     }
1269
1270     // Reset endRes of groups if beyond alignment width
1271     int alWidth = alignment.getWidth();
1272     List<SequenceGroup> groups = alignment.getGroups();
1273     if (groups != null)
1274     {
1275       for (SequenceGroup sg : groups)
1276       {
1277         if (sg.getEndRes() > alWidth)
1278         {
1279           sg.setEndRes(alWidth - 1);
1280         }
1281       }
1282     }
1283
1284     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1285     {
1286       selectionGroup.setEndRes(alWidth - 1);
1287     }
1288
1289     resetAllColourSchemes();
1290     calculator.restartWorkers();
1291     // alignment.adjustSequenceAnnotations();
1292   }
1293
1294   /**
1295    * reset scope and do calculations for all applied colourschemes on alignment
1296    */
1297   void resetAllColourSchemes()
1298   {
1299     ColourSchemeI cs = globalColourScheme;
1300     if (cs != null)
1301     {
1302       cs.alignmentChanged(alignment, null);
1303
1304       cs.setConsensus(hconsensus);
1305       if (cs.conservationApplied())
1306       {
1307         cs.setConservation(Conservation.calculateConservation("All",
1308                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1309                 alignment.getWidth(), false, getConsPercGaps(), false));
1310       }
1311     }
1312
1313     for (SequenceGroup sg : alignment.getGroups())
1314     {
1315       if (sg.cs != null)
1316       {
1317         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1318       }
1319       sg.recalcConservation();
1320     }
1321   }
1322
1323   protected void initAutoAnnotation()
1324   {
1325     // TODO: add menu option action that nulls or creates consensus object
1326     // depending on if the user wants to see the annotation or not in a
1327     // specific alignment
1328
1329     if (hconsensus == null && !isDataset)
1330     {
1331       if (!alignment.isNucleotide())
1332       {
1333         initConservation();
1334         initQuality();
1335       }
1336       else
1337       {
1338         initRNAStructure();
1339       }
1340       initConsensus();
1341     }
1342   }
1343
1344   private void initConsensus()
1345   {
1346
1347     consensus = new AlignmentAnnotation("Consensus", "PID",
1348             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1349     consensus.hasText = true;
1350     consensus.autoCalculated = true;
1351
1352     if (showConsensus)
1353     {
1354       alignment.addAnnotation(consensus);
1355     }
1356   }
1357
1358   private void initConservation()
1359   {
1360     if (showConservation)
1361     {
1362       if (conservation == null)
1363       {
1364         conservation = new AlignmentAnnotation("Conservation",
1365                 "Conservation of total alignment less than "
1366                         + getConsPercGaps() + "% gaps",
1367                 new Annotation[1], 0f, 11f,
1368                 AlignmentAnnotation.BAR_GRAPH);
1369         conservation.hasText = true;
1370         conservation.autoCalculated = true;
1371         alignment.addAnnotation(conservation);
1372       }
1373     }
1374   }
1375   private void initQuality()
1376   {
1377     if (showQuality)
1378     {
1379       if (quality == null)
1380       {
1381         quality = new AlignmentAnnotation("Quality",
1382                 "Alignment Quality based on Blosum62 scores",
1383                 new Annotation[1], 0f, 11f,
1384                 AlignmentAnnotation.BAR_GRAPH);
1385         quality.hasText = true;
1386         quality.autoCalculated = true;
1387         alignment.addAnnotation(quality);
1388       }
1389     }
1390   }
1391   private void initRNAStructure()
1392   {
1393     if (alignment.hasRNAStructure() && strucConsensus==null)
1394     {
1395       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1396               new Annotation[1], 0f, 100f,
1397               AlignmentAnnotation.BAR_GRAPH);
1398       strucConsensus.hasText = true;
1399       strucConsensus.autoCalculated = true;
1400
1401       if (showConsensus)
1402       {
1403         alignment.addAnnotation(strucConsensus);
1404       }
1405     }
1406   }
1407   /*
1408    * (non-Javadoc)
1409    * 
1410    * @see jalview.api.AlignViewportI#calcPanelHeight()
1411    */
1412   public int calcPanelHeight()
1413   {
1414     // setHeight of panels
1415     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1416     int height = 0;
1417     int charHeight = getCharHeight();
1418     if (aa != null)
1419     {
1420       BitSet graphgrp = new BitSet();
1421       for (int i = 0; i < aa.length; i++)
1422       {
1423         if (aa[i] == null)
1424         {
1425           System.err.println("Null annotation row: ignoring.");
1426           continue;
1427         }
1428         if (!aa[i].visible)
1429         {
1430           continue;
1431         }
1432         if (aa[i].graphGroup > -1)
1433         {
1434           if (graphgrp.get(aa[i].graphGroup))
1435           {
1436             continue;
1437           }
1438           else
1439           {
1440             graphgrp.set(aa[i].graphGroup);
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   @Override
1546   public Color getSequenceColour(SequenceI seq)
1547   {
1548     Color sqc=Color.white;
1549     if (sequenceColours != null)
1550     {
1551       sqc = (Color) sequenceColours.get(seq);
1552       if (sqc == null) {
1553         sqc = Color.white;
1554       }
1555     }
1556     return sqc;
1557   }
1558
1559   @Override
1560   public void setSequenceColour(SequenceI seq, Color col)
1561   {
1562     if (sequenceColours == null)
1563     {
1564       sequenceColours = new Hashtable();
1565     }
1566
1567     if (col == null)
1568     {
1569       sequenceColours.remove(seq);
1570     }
1571     else
1572     {
1573       sequenceColours.put(seq, col);
1574     }
1575   }
1576
1577   @Override
1578   public void updateSequenceIdColours()
1579   {
1580     if (sequenceColours == null)
1581     {
1582       sequenceColours = new Hashtable();
1583     }
1584     for (SequenceGroup sg : alignment.getGroups())
1585     {
1586       if (sg.idColour != null)
1587       {
1588         for (SequenceI s : sg.getSequences(getHiddenRepSequences()))
1589         {
1590           sequenceColours.put(s, sg.idColour);
1591         }
1592       }
1593     }
1594   }
1595
1596   @Override
1597   public void clearSequenceColours()
1598   {
1599     sequenceColours = null;
1600   };
1601 }