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