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