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