0c8afaa6bba9d3c7fa8a0eb7a30afdd75d3c245a
[jalview.git] / src / jalview / viewmodel / AlignmentViewport.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.viewmodel;
22
23 import jalview.analysis.AAFrequency;
24 import jalview.analysis.Conservation;
25 import jalview.api.AlignCalcManagerI;
26 import jalview.api.AlignViewportI;
27 import jalview.api.AlignmentViewPanel;
28 import jalview.datamodel.AlignmentAnnotation;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.AlignmentView;
31 import jalview.datamodel.Annotation;
32 import jalview.datamodel.ColumnSelection;
33 import jalview.datamodel.Sequence;
34 import jalview.datamodel.SequenceCollectionI;
35 import jalview.datamodel.SequenceGroup;
36 import jalview.datamodel.SequenceI;
37 import jalview.schemes.Blosum62ColourScheme;
38 import jalview.schemes.ClustalxColourScheme;
39 import jalview.schemes.ColourSchemeI;
40 import jalview.schemes.PIDColourScheme;
41 import jalview.schemes.ResidueProperties;
42 import jalview.workers.AlignCalcManager;
43 import jalview.workers.ConsensusThread;
44 import jalview.workers.StrucConsensusThread;
45
46 import java.awt.Color;
47 import java.util.ArrayList;
48 import java.util.BitSet;
49 import java.util.Hashtable;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Vector;
53
54 /**
55  * base class holding visualization and analysis attributes and common logic for
56  * an active alignment view displayed in the GUI
57  * 
58  * @author jimp
59  * 
60  */
61 public abstract class AlignmentViewport implements AlignViewportI
62 {
63   /**
64    * alignment displayed in the viewport. Please use get/setter
65    */
66   protected AlignmentI alignment;
67
68   protected String sequenceSetID;
69
70   /**
71    * probably unused indicator that view is of a dataset rather than an
72    * alignment
73    */
74   protected boolean isDataset = false;
75
76   private Map<SequenceI, SequenceCollectionI> hiddenRepSequences;
77
78   protected ColumnSelection colSel = new ColumnSelection();
79
80   public boolean autoCalculateConsensus = true;
81
82   protected boolean autoCalculateStrucConsensus = true;
83
84   protected boolean ignoreGapsInConsensusCalculation = false;
85
86   protected ColourSchemeI globalColourScheme = null;
87
88   /**
89    * gui state - changes to colour scheme propagated to all groups
90    */
91   private boolean colourAppliesToAllGroups;
92
93   /**
94    * @param value
95    *          indicating if subsequent colourscheme changes will be propagated
96    *          to all groups
97    */
98   public void setColourAppliesToAllGroups(boolean b)
99   {
100     colourAppliesToAllGroups = b;
101   }
102
103   /**
104    * 
105    * 
106    * @return flag indicating if colourchanges propagated to all groups
107    */
108   public boolean getColourAppliesToAllGroups()
109   {
110     return colourAppliesToAllGroups;
111   }
112
113   boolean abovePIDThreshold = false;
114
115   /**
116    * GUI state
117    * 
118    * @return true if percent identity threshold is applied to shading
119    */
120   public boolean getAbovePIDThreshold()
121   {
122     return abovePIDThreshold;
123   }
124
125   /**
126    * GUI state
127    * 
128    * 
129    * @param b
130    *          indicate if percent identity threshold is applied to shading
131    */
132   public void setAbovePIDThreshold(boolean b)
133   {
134     abovePIDThreshold = b;
135   }
136
137   int threshold;
138
139   /**
140    * DOCUMENT ME!
141    * 
142    * @param thresh
143    *          DOCUMENT ME!
144    */
145   public void setThreshold(int thresh)
146   {
147     threshold = thresh;
148   }
149
150   /**
151    * DOCUMENT ME!
152    * 
153    * @return DOCUMENT ME!
154    */
155   public int getThreshold()
156   {
157     return threshold;
158   }
159
160   int increment;
161
162   /**
163    * 
164    * @param inc
165    *          set the scalar for bleaching colourschemes according to degree of
166    *          conservation
167    */
168   public void setIncrement(int inc)
169   {
170     increment = inc;
171   }
172
173   /**
174    * GUI State
175    * 
176    * @return get scalar for bleaching colourschemes by conservation
177    */
178   public int getIncrement()
179   {
180     return increment;
181   }
182
183   boolean conservationColourSelected = false;
184
185   /**
186    * GUI state
187    * 
188    * @return true if conservation based shading is enabled
189    */
190   public boolean getConservationSelected()
191   {
192     return conservationColourSelected;
193   }
194
195   /**
196    * GUI state
197    * 
198    * @param b
199    *          enable conservation based shading
200    */
201   public void setConservationSelected(boolean b)
202   {
203     conservationColourSelected = b;
204   }
205
206   @Override
207   public void setGlobalColourScheme(ColourSchemeI cs)
208   {
209     // TODO: logic refactored from AlignFrame changeColour -
210     // autorecalc stuff should be changed to rely on the worker system
211     // check to see if we should implement a changeColour(cs) method rather than
212     // put th logic in here
213     // - means that caller decides if they want to just modify state and defer
214     // calculation till later or to do all calculations in thread.
215     // via changecolour
216     globalColourScheme = cs;
217     boolean recalc = false;
218     if (cs != null)
219     {
220       cs.setConservationApplied(recalc = getConservationSelected());
221       if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
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   public String getSequenceSetId()
700   {
701     if (sequenceSetID == null)
702     {
703       sequenceSetID = alignment.hashCode() + "";
704     }
705
706     return sequenceSetID;
707   }
708
709   /**
710    * unique viewId for synchronizing state (e.g. with stored Jalview Project)
711    * 
712    */
713   protected String viewId = null;
714
715   public String getViewId()
716   {
717     if (viewId == null)
718     {
719       viewId = this.getSequenceSetId() + "." + this.hashCode() + "";
720     }
721     return viewId;
722   }
723
724   public void setIgnoreGapsConsensus(boolean b, AlignmentViewPanel ap)
725   {
726     ignoreGapsInConsensusCalculation = b;
727     if (ap != null)
728     {
729       updateConsensus(ap);
730       if (globalColourScheme != null)
731       {
732         globalColourScheme.setThreshold(globalColourScheme.getThreshold(),
733                 ignoreGapsInConsensusCalculation);
734       }
735     }
736
737   }
738
739   private long sgrouphash = -1, colselhash = -1;
740
741   /**
742    * checks current SelectionGroup against record of last hash value, and
743    * updates record.
744    * 
745    * @param b
746    *          update the record of last hash value
747    * 
748    * @return true if SelectionGroup changed since last call (when b is true)
749    */
750   public boolean isSelectionGroupChanged(boolean b)
751   {
752     int hc = (selectionGroup == null || selectionGroup.getSize() == 0) ? -1
753             : selectionGroup.hashCode();
754     if (hc != -1 && hc != sgrouphash)
755     {
756       if (b)
757       {
758         sgrouphash = hc;
759       }
760       return true;
761     }
762     return false;
763   }
764
765   /**
766    * checks current colsel against record of last hash value, and optionally
767    * updates record.
768    * 
769    * @param b
770    *          update the record of last hash value
771    * @return true if colsel changed since last call (when b is true)
772    */
773   public boolean isColSelChanged(boolean b)
774   {
775     int hc = (colSel == null || colSel.size() == 0) ? -1 : colSel
776             .hashCode();
777     if (hc != -1 && hc != colselhash)
778     {
779       if (b)
780       {
781         colselhash = hc;
782       }
783       return true;
784     }
785     return false;
786   }
787
788   @Override
789   public boolean getIgnoreGapsConsensus()
790   {
791     return ignoreGapsInConsensusCalculation;
792   }
793
794   // / property change stuff
795
796   // JBPNote Prolly only need this in the applet version.
797   private final java.beans.PropertyChangeSupport changeSupport = new java.beans.PropertyChangeSupport(
798           this);
799
800   protected boolean showConservation = true;
801
802   protected boolean showQuality = true;
803
804   protected boolean showConsensus = true;
805
806   Hashtable sequenceColours;
807
808   /**
809    * Property change listener for changes in alignment
810    * 
811    * @param listener
812    *          DOCUMENT ME!
813    */
814   public void addPropertyChangeListener(
815           java.beans.PropertyChangeListener listener)
816   {
817     changeSupport.addPropertyChangeListener(listener);
818   }
819
820   /**
821    * DOCUMENT ME!
822    * 
823    * @param listener
824    *          DOCUMENT ME!
825    */
826   public void removePropertyChangeListener(
827           java.beans.PropertyChangeListener listener)
828   {
829     changeSupport.removePropertyChangeListener(listener);
830   }
831
832   /**
833    * Property change listener for changes in alignment
834    * 
835    * @param prop
836    *          DOCUMENT ME!
837    * @param oldvalue
838    *          DOCUMENT ME!
839    * @param newvalue
840    *          DOCUMENT ME!
841    */
842   public void firePropertyChange(String prop, Object oldvalue,
843           Object newvalue)
844   {
845     changeSupport.firePropertyChange(prop, oldvalue, newvalue);
846   }
847
848   // common hide/show column stuff
849
850   public void hideSelectedColumns()
851   {
852     if (colSel.size() < 1)
853     {
854       return;
855     }
856
857     colSel.hideSelectedColumns();
858     setSelectionGroup(null);
859
860     hasHiddenColumns = true;
861   }
862
863   public void hideColumns(int start, int end)
864   {
865     if (start == end)
866     {
867       colSel.hideColumns(start);
868     }
869     else
870     {
871       colSel.hideColumns(start, end);
872     }
873
874     hasHiddenColumns = true;
875   }
876
877   public void showColumn(int col)
878   {
879     colSel.revealHiddenColumns(col);
880     if (colSel.getHiddenColumns() == null)
881     {
882       hasHiddenColumns = false;
883     }
884   }
885
886   public void showAllHiddenColumns()
887   {
888     colSel.revealAllHiddenColumns();
889     hasHiddenColumns = false;
890   }
891
892   // common hide/show seq stuff
893   public void showAllHiddenSeqs()
894   {
895     if (alignment.getHiddenSequences().getSize() > 0)
896     {
897       if (selectionGroup == null)
898       {
899         selectionGroup = new SequenceGroup();
900         selectionGroup.setEndRes(alignment.getWidth() - 1);
901       }
902       Vector tmp = alignment.getHiddenSequences().showAll(
903               hiddenRepSequences);
904       for (int t = 0; t < tmp.size(); t++)
905       {
906         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
907       }
908
909       hasHiddenRows = false;
910       hiddenRepSequences = null;
911
912       firePropertyChange("alignment", null, alignment.getSequences());
913       // used to set hasHiddenRows/hiddenRepSequences here, after the property
914       // changed event
915       sendSelection();
916     }
917   }
918
919   public void showSequence(int index)
920   {
921     Vector tmp = alignment.getHiddenSequences().showSequence(index,
922             hiddenRepSequences);
923     if (tmp.size() > 0)
924     {
925       if (selectionGroup == null)
926       {
927         selectionGroup = new SequenceGroup();
928         selectionGroup.setEndRes(alignment.getWidth() - 1);
929       }
930
931       for (int t = 0; t < tmp.size(); t++)
932       {
933         selectionGroup.addSequence((SequenceI) tmp.elementAt(t), false);
934       }
935       // JBPNote: refactor: only update flag if we modified visiblity (used to
936       // do this regardless)
937       if (alignment.getHiddenSequences().getSize() < 1)
938       {
939         hasHiddenRows = false;
940       }
941       firePropertyChange("alignment", null, alignment.getSequences());
942       sendSelection();
943     }
944   }
945
946   public void hideAllSelectedSeqs()
947   {
948     if (selectionGroup == null || selectionGroup.getSize() < 1)
949     {
950       return;
951     }
952
953     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
954
955     hideSequence(seqs);
956
957     setSelectionGroup(null);
958   }
959
960   public void hideSequence(SequenceI[] seq)
961   {
962     if (seq != null)
963     {
964       for (int i = 0; i < seq.length; i++)
965       {
966         alignment.getHiddenSequences().hideSequence(seq[i]);
967       }
968       hasHiddenRows = true;
969       firePropertyChange("alignment", null, alignment.getSequences());
970     }
971   }
972
973   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
974   {
975     int sSize = sg.getSize();
976     if (sSize < 2)
977     {
978       return;
979     }
980
981     if (hiddenRepSequences == null)
982     {
983       hiddenRepSequences = new Hashtable();
984     }
985
986     hiddenRepSequences.put(repSequence, sg);
987
988     // Hide all sequences except the repSequence
989     SequenceI[] seqs = new SequenceI[sSize - 1];
990     int index = 0;
991     for (int i = 0; i < sSize; i++)
992     {
993       if (sg.getSequenceAt(i) != repSequence)
994       {
995         if (index == sSize - 1)
996         {
997           return;
998         }
999
1000         seqs[index++] = sg.getSequenceAt(i);
1001       }
1002     }
1003     sg.setSeqrep(repSequence); // note: not done in 2.7applet
1004     sg.setHidereps(true); // note: not done in 2.7applet
1005     hideSequence(seqs);
1006
1007   }
1008
1009   public boolean isHiddenRepSequence(SequenceI seq)
1010   {
1011     return hiddenRepSequences != null
1012             && hiddenRepSequences.containsKey(seq);
1013   }
1014
1015   public SequenceGroup getRepresentedSequences(SequenceI seq)
1016   {
1017     return (SequenceGroup) (hiddenRepSequences == null ? null
1018             : hiddenRepSequences.get(seq));
1019   }
1020
1021   public int adjustForHiddenSeqs(int alignmentIndex)
1022   {
1023     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1024             alignmentIndex);
1025   }
1026
1027   // Selection manipulation
1028   /**
1029    * broadcast selection to any interested parties
1030    */
1031   public abstract void sendSelection();
1032
1033   public void invertColumnSelection()
1034   {
1035     colSel.invertColumnSelection(0, alignment.getWidth());
1036   }
1037
1038   /**
1039    * This method returns an array of new SequenceI objects derived from the
1040    * whole alignment or just the current selection with start and end points
1041    * adjusted
1042    * 
1043    * @note if you need references to the actual SequenceI objects in the
1044    *       alignment or currently selected then use getSequenceSelection()
1045    * @return selection as new sequenceI objects
1046    */
1047   public SequenceI[] getSelectionAsNewSequence()
1048   {
1049     SequenceI[] sequences;
1050     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1051     // this was the only caller in the applet for this method
1052     // JBPNote: in applet, this method returned references to the alignment
1053     // sequences, and it did not honour the presence/absence of annotation
1054     // attached to the alignment (probably!)
1055     if (selectionGroup == null || selectionGroup.getSize() == 0)
1056     {
1057       sequences = alignment.getSequencesArray();
1058       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1059       for (int i = 0; i < sequences.length; i++)
1060       {
1061         sequences[i] = new Sequence(sequences[i], annots); // construct new
1062         // sequence with
1063         // subset of visible
1064         // annotation
1065       }
1066     }
1067     else
1068     {
1069       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1070     }
1071
1072     return sequences;
1073   }
1074
1075   /**
1076    * get the currently selected sequence objects or all the sequences in the
1077    * alignment.
1078    * 
1079    * @return array of references to sequence objects
1080    */
1081   @Override
1082   public SequenceI[] getSequenceSelection()
1083   {
1084     SequenceI[] sequences = null;
1085     if (selectionGroup != null)
1086     {
1087       sequences = selectionGroup.getSequencesInOrder(alignment);
1088     }
1089     if (sequences == null)
1090     {
1091       sequences = alignment.getSequencesArray();
1092     }
1093     return sequences;
1094   }
1095
1096   /**
1097    * This method returns the visible alignment as text, as seen on the GUI, ie
1098    * if columns are hidden they will not be returned in the result. Use this for
1099    * calculating trees, PCA, redundancy etc on views which contain hidden
1100    * columns.
1101    * 
1102    * @return String[]
1103    */
1104   @Override
1105   public jalview.datamodel.CigarArray getViewAsCigars(
1106           boolean selectedRegionOnly)
1107   {
1108     return new jalview.datamodel.CigarArray(alignment,
1109             (hasHiddenColumns ? colSel : null),
1110             (selectedRegionOnly ? selectionGroup : null));
1111   }
1112
1113   /**
1114    * return a compact representation of the current alignment selection to pass
1115    * to an analysis function
1116    * 
1117    * @param selectedOnly
1118    *          boolean true to just return the selected view
1119    * @return AlignmentView
1120    */
1121   @Override
1122   public jalview.datamodel.AlignmentView getAlignmentView(
1123           boolean selectedOnly)
1124   {
1125     return getAlignmentView(selectedOnly, false);
1126   }
1127
1128   /**
1129    * return a compact representation of the current alignment selection to pass
1130    * to an analysis function
1131    * 
1132    * @param selectedOnly
1133    *          boolean true to just return the selected view
1134    * @param markGroups
1135    *          boolean true to annotate the alignment view with groups on the
1136    *          alignment (and intersecting with selected region if selectedOnly
1137    *          is true)
1138    * @return AlignmentView
1139    */
1140   @Override
1141   public jalview.datamodel.AlignmentView getAlignmentView(
1142           boolean selectedOnly, boolean markGroups)
1143   {
1144     return new AlignmentView(alignment, colSel, selectionGroup,
1145             hasHiddenColumns, selectedOnly, markGroups);
1146   }
1147
1148   /**
1149    * This method returns the visible alignment as text, as seen on the GUI, ie
1150    * if columns are hidden they will not be returned in the result. Use this for
1151    * calculating trees, PCA, redundancy etc on views which contain hidden
1152    * columns.
1153    * 
1154    * @return String[]
1155    */
1156   @Override
1157   public String[] getViewAsString(boolean selectedRegionOnly)
1158   {
1159     String[] selection = null;
1160     SequenceI[] seqs = null;
1161     int i, iSize;
1162     int start = 0, end = 0;
1163     if (selectedRegionOnly && selectionGroup != null)
1164     {
1165       iSize = selectionGroup.getSize();
1166       seqs = selectionGroup.getSequencesInOrder(alignment);
1167       start = selectionGroup.getStartRes();
1168       end = selectionGroup.getEndRes() + 1;
1169     }
1170     else
1171     {
1172       iSize = alignment.getHeight();
1173       seqs = alignment.getSequencesArray();
1174       end = alignment.getWidth();
1175     }
1176
1177     selection = new String[iSize];
1178     if (hasHiddenColumns)
1179     {
1180       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1181     }
1182     else
1183     {
1184       for (i = 0; i < iSize; i++)
1185       {
1186         selection[i] = seqs[i].getSequenceAsString(start, end);
1187       }
1188
1189     }
1190     return selection;
1191   }
1192
1193   /**
1194    * return visible region boundaries within given column range
1195    * 
1196    * @param min
1197    *          first column (inclusive, from 0)
1198    * @param max
1199    *          last column (exclusive)
1200    * @return int[][] range of {start,end} visible positions
1201    */
1202   public int[][] getVisibleRegionBoundaries(int min, int max)
1203   {
1204     Vector regions = new Vector();
1205     int start = min;
1206     int end = max;
1207
1208     do
1209     {
1210       if (hasHiddenColumns)
1211       {
1212         if (start == 0)
1213         {
1214           start = colSel.adjustForHiddenColumns(start);
1215         }
1216
1217         end = colSel.getHiddenBoundaryRight(start);
1218         if (start == end)
1219         {
1220           end = max;
1221         }
1222         if (end > max)
1223         {
1224           end = max;
1225         }
1226       }
1227
1228       regions.addElement(new int[]
1229       { start, end });
1230
1231       if (hasHiddenColumns)
1232       {
1233         start = colSel.adjustForHiddenColumns(end);
1234         start = colSel.getHiddenBoundaryLeft(start) + 1;
1235       }
1236     } while (end < max);
1237
1238     int[][] startEnd = new int[regions.size()][2];
1239
1240     regions.copyInto(startEnd);
1241
1242     return startEnd;
1243
1244   }
1245
1246   /**
1247    * @return the padGaps
1248    */
1249   public boolean isPadGaps()
1250   {
1251     return padGaps;
1252   }
1253
1254   /**
1255    * @param padGaps
1256    *          the padGaps to set
1257    */
1258   public void setPadGaps(boolean padGaps)
1259   {
1260     this.padGaps = padGaps;
1261   }
1262
1263   /**
1264    * apply any post-edit constraints and trigger any calculations needed after
1265    * an edit has been performed on the alignment
1266    * 
1267    * @param ap
1268    */
1269   public void alignmentChanged(AlignmentViewPanel ap)
1270   {
1271     if (isPadGaps())
1272     {
1273       alignment.padGaps();
1274     }
1275     if (autoCalculateConsensus)
1276     {
1277       updateConsensus(ap);
1278     }
1279     if (hconsensus != null && autoCalculateConsensus)
1280     {
1281       updateConservation(ap);
1282     }
1283     if (autoCalculateStrucConsensus)
1284     {
1285       updateStrucConsensus(ap);
1286     }
1287
1288     // Reset endRes of groups if beyond alignment width
1289     int alWidth = alignment.getWidth();
1290     List<SequenceGroup> groups = alignment.getGroups();
1291     if (groups != null)
1292     {
1293       for (SequenceGroup sg : groups)
1294       {
1295         if (sg.getEndRes() > alWidth)
1296         {
1297           sg.setEndRes(alWidth - 1);
1298         }
1299       }
1300     }
1301
1302     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1303     {
1304       selectionGroup.setEndRes(alWidth - 1);
1305     }
1306
1307     resetAllColourSchemes();
1308     calculator.restartWorkers();
1309     // alignment.adjustSequenceAnnotations();
1310   }
1311
1312   /**
1313    * reset scope and do calculations for all applied colourschemes on alignment
1314    */
1315   void resetAllColourSchemes()
1316   {
1317     ColourSchemeI cs = globalColourScheme;
1318     if (cs != null)
1319     {
1320       cs.alignmentChanged(alignment, hiddenRepSequences);
1321
1322       cs.setConsensus(hconsensus);
1323       if (cs.conservationApplied())
1324       {
1325         cs.setConservation(Conservation.calculateConservation("All",
1326                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1327                 alignment.getWidth(), false, getConsPercGaps(), false));
1328       }
1329     }
1330
1331     for (SequenceGroup sg : alignment.getGroups())
1332     {
1333       if (sg.cs != null)
1334       {
1335         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1336       }
1337       sg.recalcConservation();
1338     }
1339   }
1340
1341   protected void initAutoAnnotation()
1342   {
1343     // TODO: add menu option action that nulls or creates consensus object
1344     // depending on if the user wants to see the annotation or not in a
1345     // specific alignment
1346
1347     if (hconsensus == null && !isDataset)
1348     {
1349       if (!alignment.isNucleotide())
1350       {
1351         initConservation();
1352         initQuality();
1353       }
1354       else
1355       {
1356         initRNAStructure();
1357       }
1358       initConsensus();
1359     }
1360   }
1361
1362   private void initConsensus()
1363   {
1364
1365     consensus = new AlignmentAnnotation("Consensus", "PID",
1366             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1367     consensus.hasText = true;
1368     consensus.autoCalculated = true;
1369
1370     if (showConsensus)
1371     {
1372       alignment.addAnnotation(consensus);
1373     }
1374   }
1375
1376   private void initConservation()
1377   {
1378     if (showConservation)
1379     {
1380       if (conservation == null)
1381       {
1382         conservation = new AlignmentAnnotation("Conservation",
1383                 "Conservation of total alignment less than "
1384                         + getConsPercGaps() + "% gaps", new Annotation[1],
1385                 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1386         conservation.hasText = true;
1387         conservation.autoCalculated = true;
1388         alignment.addAnnotation(conservation);
1389       }
1390     }
1391   }
1392
1393   private void initQuality()
1394   {
1395     if (showQuality)
1396     {
1397       if (quality == null)
1398       {
1399         quality = new AlignmentAnnotation("Quality",
1400                 "Alignment Quality based on Blosum62 scores",
1401                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1402         quality.hasText = true;
1403         quality.autoCalculated = true;
1404         alignment.addAnnotation(quality);
1405       }
1406     }
1407   }
1408
1409   private void initRNAStructure()
1410   {
1411     if (alignment.hasRNAStructure() && strucConsensus == null)
1412     {
1413       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1414               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1415       strucConsensus.hasText = true;
1416       strucConsensus.autoCalculated = true;
1417
1418       if (showConsensus)
1419       {
1420         alignment.addAnnotation(strucConsensus);
1421       }
1422     }
1423   }
1424
1425   /*
1426    * (non-Javadoc)
1427    * 
1428    * @see jalview.api.AlignViewportI#calcPanelHeight()
1429    */
1430   public int calcPanelHeight()
1431   {
1432     // setHeight of panels
1433     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1434     int height = 0;
1435     int charHeight = getCharHeight();
1436     if (aa != null)
1437     {
1438       BitSet graphgrp = new BitSet();
1439       for (int i = 0; i < aa.length; i++)
1440       {
1441         if (aa[i] == null)
1442         {
1443           System.err.println("Null annotation row: ignoring.");
1444           continue;
1445         }
1446         if (!aa[i].visible)
1447         {
1448           continue;
1449         }
1450         if (aa[i].graphGroup > -1)
1451         {
1452           if (graphgrp.get(aa[i].graphGroup))
1453           {
1454             continue;
1455           }
1456           else
1457           {
1458             graphgrp.set(aa[i].graphGroup);
1459           }
1460         }
1461         aa[i].height = 0;
1462
1463         if (aa[i].hasText)
1464         {
1465           aa[i].height += charHeight;
1466         }
1467
1468         if (aa[i].hasIcons)
1469         {
1470           aa[i].height += 16;
1471         }
1472
1473         if (aa[i].graph > 0)
1474         {
1475           aa[i].height += aa[i].graphHeight;
1476         }
1477
1478         if (aa[i].height == 0)
1479         {
1480           aa[i].height = 20;
1481         }
1482
1483         height += aa[i].height;
1484       }
1485     }
1486     if (height == 0)
1487     {
1488       // set minimum
1489       height = 20;
1490     }
1491     return height;
1492   }
1493
1494   @Override
1495   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
1496           boolean preserveNewGroupSettings)
1497   {
1498     boolean updateCalcs = false;
1499     boolean conv = isShowGroupConservation();
1500     boolean cons = isShowGroupConsensus();
1501     boolean showprf = isShowSequenceLogo();
1502     boolean showConsHist = isShowConsensusHistogram();
1503     boolean normLogo = isNormaliseSequenceLogo();
1504
1505     /**
1506      * TODO reorder the annotation rows according to group/sequence ordering on
1507      * alignment
1508      */
1509     boolean sortg = true;
1510
1511     // remove old automatic annotation
1512     // add any new annotation
1513
1514     // intersect alignment annotation with alignment groups
1515
1516     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
1517     List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
1518     if (aan != null)
1519     {
1520       for (int an = 0; an < aan.length; an++)
1521       {
1522         if (aan[an].autoCalculated && aan[an].groupRef != null)
1523         {
1524           oldrfs.add(aan[an].groupRef);
1525           alignment.deleteAnnotation(aan[an], false);
1526         }
1527       }
1528     }
1529     if (alignment.getGroups() != null)
1530     {
1531       for (SequenceGroup sg : alignment.getGroups())
1532       {
1533         updateCalcs = false;
1534         if (applyGlobalSettings
1535                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
1536         {
1537           // set defaults for this group's conservation/consensus
1538           sg.setshowSequenceLogo(showprf);
1539           sg.setShowConsensusHistogram(showConsHist);
1540           sg.setNormaliseSequenceLogo(normLogo);
1541         }
1542         if (conv)
1543         {
1544           updateCalcs = true;
1545           alignment.addAnnotation(sg.getConservationRow(), 0);
1546         }
1547         if (cons)
1548         {
1549           updateCalcs = true;
1550           alignment.addAnnotation(sg.getConsensus(), 0);
1551         }
1552         // refresh the annotation rows
1553         if (updateCalcs)
1554         {
1555           sg.recalcConservation();
1556         }
1557       }
1558     }
1559     oldrfs.clear();
1560   }
1561
1562   @Override
1563   public Color getSequenceColour(SequenceI seq)
1564   {
1565     Color sqc = Color.white;
1566     if (sequenceColours != null)
1567     {
1568       sqc = (Color) sequenceColours.get(seq);
1569       if (sqc == null)
1570       {
1571         sqc = Color.white;
1572       }
1573     }
1574     return sqc;
1575   }
1576
1577   @Override
1578   public void setSequenceColour(SequenceI seq, Color col)
1579   {
1580     if (sequenceColours == null)
1581     {
1582       sequenceColours = new Hashtable();
1583     }
1584
1585     if (col == null)
1586     {
1587       sequenceColours.remove(seq);
1588     }
1589     else
1590     {
1591       sequenceColours.put(seq, col);
1592     }
1593   }
1594
1595   @Override
1596   public void updateSequenceIdColours()
1597   {
1598     if (sequenceColours == null)
1599     {
1600       sequenceColours = new Hashtable();
1601     }
1602     for (SequenceGroup sg : alignment.getGroups())
1603     {
1604       if (sg.idColour != null)
1605       {
1606         for (SequenceI s : sg.getSequences(getHiddenRepSequences()))
1607         {
1608           sequenceColours.put(s, sg.idColour);
1609         }
1610       }
1611     }
1612   }
1613
1614   @Override
1615   public void clearSequenceColours()
1616   {
1617     sequenceColours = null;
1618   };
1619 }