Merge branch 'develop' into JAL-1483_featureBasedTreeCalc
[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   @Override
1248   public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(boolean selectedOnly)
1249   {
1250     ArrayList<AlignmentAnnotation> ala = new ArrayList<AlignmentAnnotation>();
1251     AlignmentAnnotation[] aa;
1252     if ((aa=alignment.getAlignmentAnnotation())!=null)
1253     {
1254       for (AlignmentAnnotation annot:aa)
1255       {
1256         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
1257         if (selectedOnly && selectionGroup!=null)
1258         {
1259           colSel.makeVisibleAnnotation(selectionGroup.getStartRes(), selectionGroup.getEndRes(),clone);
1260         } else {
1261           colSel.makeVisibleAnnotation(clone);
1262         }
1263         ala.add(clone);
1264       }
1265     }
1266     return ala;
1267   }
1268
1269   /**
1270    * @return the padGaps
1271    */
1272   public boolean isPadGaps()
1273   {
1274     return padGaps;
1275   }
1276
1277   /**
1278    * @param padGaps
1279    *          the padGaps to set
1280    */
1281   public void setPadGaps(boolean padGaps)
1282   {
1283     this.padGaps = padGaps;
1284   }
1285
1286   /**
1287    * apply any post-edit constraints and trigger any calculations needed after
1288    * an edit has been performed on the alignment
1289    * 
1290    * @param ap
1291    */
1292   public void alignmentChanged(AlignmentViewPanel ap)
1293   {
1294     if (isPadGaps())
1295     {
1296       alignment.padGaps();
1297     }
1298     if (autoCalculateConsensus)
1299     {
1300       updateConsensus(ap);
1301     }
1302     if (hconsensus != null && autoCalculateConsensus)
1303     {
1304       updateConservation(ap);
1305     }
1306     if (autoCalculateStrucConsensus)
1307     {
1308       updateStrucConsensus(ap);
1309     }
1310
1311     // Reset endRes of groups if beyond alignment width
1312     int alWidth = alignment.getWidth();
1313     List<SequenceGroup> groups = alignment.getGroups();
1314     if (groups != null)
1315     {
1316       for (SequenceGroup sg : groups)
1317       {
1318         if (sg.getEndRes() > alWidth)
1319         {
1320           sg.setEndRes(alWidth - 1);
1321         }
1322       }
1323     }
1324
1325     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1326     {
1327       selectionGroup.setEndRes(alWidth - 1);
1328     }
1329
1330     resetAllColourSchemes();
1331     calculator.restartWorkers();
1332     // alignment.adjustSequenceAnnotations();
1333   }
1334
1335   /**
1336    * reset scope and do calculations for all applied colourschemes on alignment
1337    */
1338   void resetAllColourSchemes()
1339   {
1340     ColourSchemeI cs = globalColourScheme;
1341     if (cs != null)
1342     {
1343       cs.alignmentChanged(alignment, hiddenRepSequences);
1344
1345       cs.setConsensus(hconsensus);
1346       if (cs.conservationApplied())
1347       {
1348         cs.setConservation(Conservation.calculateConservation("All",
1349                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1350                 alignment.getWidth(), false, getConsPercGaps(), false));
1351       }
1352     }
1353
1354     for (SequenceGroup sg : alignment.getGroups())
1355     {
1356       if (sg.cs != null)
1357       {
1358         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1359       }
1360       sg.recalcConservation();
1361     }
1362   }
1363
1364   protected void initAutoAnnotation()
1365   {
1366     // TODO: add menu option action that nulls or creates consensus object
1367     // depending on if the user wants to see the annotation or not in a
1368     // specific alignment
1369
1370     if (hconsensus == null && !isDataset)
1371     {
1372       if (!alignment.isNucleotide())
1373       {
1374         initConservation();
1375         initQuality();
1376       }
1377       else
1378       {
1379         initRNAStructure();
1380       }
1381       initConsensus();
1382     }
1383   }
1384
1385   private void initConsensus()
1386   {
1387
1388     consensus = new AlignmentAnnotation("Consensus", "PID",
1389             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1390     consensus.hasText = true;
1391     consensus.autoCalculated = true;
1392
1393     if (showConsensus)
1394     {
1395       alignment.addAnnotation(consensus);
1396     }
1397   }
1398
1399   private void initConservation()
1400   {
1401     if (showConservation)
1402     {
1403       if (conservation == null)
1404       {
1405         conservation = new AlignmentAnnotation("Conservation",
1406                 "Conservation of total alignment less than "
1407                         + getConsPercGaps() + "% gaps", new Annotation[1],
1408                 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1409         conservation.hasText = true;
1410         conservation.autoCalculated = true;
1411         alignment.addAnnotation(conservation);
1412       }
1413     }
1414   }
1415
1416   private void initQuality()
1417   {
1418     if (showQuality)
1419     {
1420       if (quality == null)
1421       {
1422         quality = new AlignmentAnnotation("Quality",
1423                 "Alignment Quality based on Blosum62 scores",
1424                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1425         quality.hasText = true;
1426         quality.autoCalculated = true;
1427         alignment.addAnnotation(quality);
1428       }
1429     }
1430   }
1431
1432   private void initRNAStructure()
1433   {
1434     if (alignment.hasRNAStructure() && strucConsensus == null)
1435     {
1436       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1437               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1438       strucConsensus.hasText = true;
1439       strucConsensus.autoCalculated = true;
1440
1441       if (showConsensus)
1442       {
1443         alignment.addAnnotation(strucConsensus);
1444       }
1445     }
1446   }
1447
1448   /*
1449    * (non-Javadoc)
1450    * 
1451    * @see jalview.api.AlignViewportI#calcPanelHeight()
1452    */
1453   public int calcPanelHeight()
1454   {
1455     // setHeight of panels
1456     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1457     int height = 0;
1458     int charHeight = getCharHeight();
1459     if (aa != null)
1460     {
1461       BitSet graphgrp = new BitSet();
1462       for (int i = 0; i < aa.length; i++)
1463       {
1464         if (aa[i] == null)
1465         {
1466           System.err.println("Null annotation row: ignoring.");
1467           continue;
1468         }
1469         if (!aa[i].visible)
1470         {
1471           continue;
1472         }
1473         if (aa[i].graphGroup > -1)
1474         {
1475           if (graphgrp.get(aa[i].graphGroup))
1476           {
1477             continue;
1478           }
1479           else
1480           {
1481             graphgrp.set(aa[i].graphGroup);
1482           }
1483         }
1484         aa[i].height = 0;
1485
1486         if (aa[i].hasText)
1487         {
1488           aa[i].height += charHeight;
1489         }
1490
1491         if (aa[i].hasIcons)
1492         {
1493           aa[i].height += 16;
1494         }
1495
1496         if (aa[i].graph > 0)
1497         {
1498           aa[i].height += aa[i].graphHeight;
1499         }
1500
1501         if (aa[i].height == 0)
1502         {
1503           aa[i].height = 20;
1504         }
1505
1506         height += aa[i].height;
1507       }
1508     }
1509     if (height == 0)
1510     {
1511       // set minimum
1512       height = 20;
1513     }
1514     return height;
1515   }
1516
1517   @Override
1518   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
1519           boolean preserveNewGroupSettings)
1520   {
1521     boolean updateCalcs = false;
1522     boolean conv = isShowGroupConservation();
1523     boolean cons = isShowGroupConsensus();
1524     boolean showprf = isShowSequenceLogo();
1525     boolean showConsHist = isShowConsensusHistogram();
1526     boolean normLogo = isNormaliseSequenceLogo();
1527
1528     /**
1529      * TODO reorder the annotation rows according to group/sequence ordering on
1530      * alignment
1531      */
1532     boolean sortg = true;
1533
1534     // remove old automatic annotation
1535     // add any new annotation
1536
1537     // intersect alignment annotation with alignment groups
1538
1539     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
1540     List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
1541     if (aan != null)
1542     {
1543       for (int an = 0; an < aan.length; an++)
1544       {
1545         if (aan[an].autoCalculated && aan[an].groupRef != null)
1546         {
1547           oldrfs.add(aan[an].groupRef);
1548           alignment.deleteAnnotation(aan[an], false);
1549         }
1550       }
1551     }
1552     if (alignment.getGroups() != null)
1553     {
1554       for (SequenceGroup sg : alignment.getGroups())
1555       {
1556         updateCalcs = false;
1557         if (applyGlobalSettings
1558                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
1559         {
1560           // set defaults for this group's conservation/consensus
1561           sg.setshowSequenceLogo(showprf);
1562           sg.setShowConsensusHistogram(showConsHist);
1563           sg.setNormaliseSequenceLogo(normLogo);
1564         }
1565         if (conv)
1566         {
1567           updateCalcs = true;
1568           alignment.addAnnotation(sg.getConservationRow(), 0);
1569         }
1570         if (cons)
1571         {
1572           updateCalcs = true;
1573           alignment.addAnnotation(sg.getConsensus(), 0);
1574         }
1575         // refresh the annotation rows
1576         if (updateCalcs)
1577         {
1578           sg.recalcConservation();
1579         }
1580       }
1581     }
1582     oldrfs.clear();
1583   }
1584
1585   @Override
1586   public Color getSequenceColour(SequenceI seq)
1587   {
1588     Color sqc = Color.white;
1589     if (sequenceColours != null)
1590     {
1591       sqc = (Color) sequenceColours.get(seq);
1592       if (sqc == null)
1593       {
1594         sqc = Color.white;
1595       }
1596     }
1597     return sqc;
1598   }
1599
1600   @Override
1601   public void setSequenceColour(SequenceI seq, Color col)
1602   {
1603     if (sequenceColours == null)
1604     {
1605       sequenceColours = new Hashtable();
1606     }
1607
1608     if (col == null)
1609     {
1610       sequenceColours.remove(seq);
1611     }
1612     else
1613     {
1614       sequenceColours.put(seq, col);
1615     }
1616   }
1617
1618   @Override
1619   public void updateSequenceIdColours()
1620   {
1621     if (sequenceColours == null)
1622     {
1623       sequenceColours = new Hashtable();
1624     }
1625     for (SequenceGroup sg : alignment.getGroups())
1626     {
1627       if (sg.idColour != null)
1628       {
1629         for (SequenceI s : sg.getSequences(getHiddenRepSequences()))
1630         {
1631           sequenceColours.put(s, sg.idColour);
1632         }
1633       }
1634     }
1635   }
1636
1637   @Override
1638   public void clearSequenceColours()
1639   {
1640     sequenceColours = null;
1641   };
1642
1643   FeaturesDisplayedI featuresDisplayed = null;
1644
1645   @Override
1646   public FeaturesDisplayedI getFeaturesDisplayed()
1647   {
1648     return featuresDisplayed;
1649   }
1650
1651   public void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI)
1652   {
1653     featuresDisplayed = featuresDisplayedI;
1654   }
1655
1656   public boolean areFeaturesDisplayed()
1657   {
1658     return featuresDisplayed != null && featuresDisplayed.getRegisterdFeaturesCount()>0;
1659   }
1660
1661   /**
1662    * display setting for showing/hiding sequence features on alignment view
1663    */
1664   boolean showSequenceFeatures = false;
1665
1666   /**
1667    * set the flag
1668    * 
1669    * @param b
1670    *          features are displayed if true
1671    */
1672   @Override
1673   public void setShowSequenceFeatures(boolean b)
1674   {
1675     showSequenceFeatures = b;
1676   }
1677   @Override
1678   public boolean isShowSequenceFeatures()
1679   {
1680     return showSequenceFeatures;
1681   }
1682
1683   boolean showSeqFeaturesHeight;
1684
1685   public void setShowSequenceFeaturesHeight(boolean selected)
1686   {
1687     showSeqFeaturesHeight = selected;
1688   }
1689
1690   public boolean isShowSequenceFeaturesHeight()
1691   {
1692     return showSeqFeaturesHeight;
1693   }
1694
1695
1696 }