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