1b42faf037a32f8ec693723f9c2d4c12a74abbd6
[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.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.AlignmentView;
30 import jalview.datamodel.Annotation;
31 import jalview.datamodel.ColumnSelection;
32 import jalview.datamodel.Sequence;
33 import jalview.datamodel.SequenceCollectionI;
34 import jalview.datamodel.SequenceGroup;
35 import jalview.datamodel.SequenceI;
36 import jalview.schemes.Blosum62ColourScheme;
37 import jalview.schemes.ColourSchemeI;
38 import jalview.schemes.PIDColourScheme;
39 import jalview.schemes.ResidueProperties;
40 import jalview.workers.AlignCalcManager;
41 import jalview.workers.ConsensusThread;
42 import jalview.workers.StrucConsensusThread;
43
44 import java.awt.Color;
45 import java.util.ArrayList;
46 import java.util.BitSet;
47 import java.util.Hashtable;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Vector;
51
52 /**
53  * base class holding visualization and analysis attributes and common logic for
54  * an active alignment view displayed in the GUI
55  * 
56  * @author jimp
57  * 
58  */
59 public abstract class AlignmentViewport implements AlignViewportI
60 {
61   /**
62    * alignment displayed in the viewport. Please use get/setter
63    */
64   protected AlignmentI alignment;
65
66   protected String sequenceSetID;
67
68   /**
69    * probably unused indicator that view is of a dataset rather than an
70    * alignment
71    */
72   protected boolean isDataset = false;
73
74   private Map<SequenceI, SequenceCollectionI> hiddenRepSequences;
75
76   protected ColumnSelection colSel = new ColumnSelection();
77
78   public boolean autoCalculateConsensus = true;
79
80   protected boolean autoCalculateStrucConsensus = true;
81
82   protected boolean ignoreGapsInConsensusCalculation = false;
83
84   protected ColourSchemeI globalColourScheme = null;
85
86   /**
87    * gui state - changes to colour scheme propagated to all groups
88    */
89   private boolean colourAppliesToAllGroups;
90
91   /**
92    * @param value
93    *          indicating if subsequent colourscheme changes will be propagated
94    *          to all groups
95    */
96   public void setColourAppliesToAllGroups(boolean b)
97   {
98     colourAppliesToAllGroups = b;
99   }
100
101   /**
102    * 
103    * 
104    * @return flag indicating if colourchanges propagated to all groups
105    */
106   public boolean getColourAppliesToAllGroups()
107   {
108     return colourAppliesToAllGroups;
109   }
110
111   boolean abovePIDThreshold = false;
112
113   /**
114    * GUI state
115    * 
116    * @return true if percent identity threshold is applied to shading
117    */
118   public boolean getAbovePIDThreshold()
119   {
120     return abovePIDThreshold;
121   }
122
123   /**
124    * GUI state
125    * 
126    * 
127    * @param b
128    *          indicate if percent identity threshold is applied to shading
129    */
130   public void setAbovePIDThreshold(boolean b)
131   {
132     abovePIDThreshold = b;
133   }
134
135   int threshold;
136
137   /**
138    * DOCUMENT ME!
139    * 
140    * @param thresh
141    *          DOCUMENT ME!
142    */
143   public void setThreshold(int thresh)
144   {
145     threshold = thresh;
146   }
147
148   /**
149    * DOCUMENT ME!
150    * 
151    * @return DOCUMENT ME!
152    */
153   public int getThreshold()
154   {
155     return threshold;
156   }
157
158   int increment;
159
160   /**
161    * 
162    * @param inc
163    *          set the scalar for bleaching colourschemes according to degree of
164    *          conservation
165    */
166   public void setIncrement(int inc)
167   {
168     increment = inc;
169   }
170
171   /**
172    * GUI State
173    * 
174    * @return get scalar for bleaching colourschemes by conservation
175    */
176   public int getIncrement()
177   {
178     return increment;
179   }
180
181   boolean conservationColourSelected = false;
182
183   /**
184    * GUI state
185    * 
186    * @return true if conservation based shading is enabled
187    */
188   public boolean getConservationSelected()
189   {
190     return conservationColourSelected;
191   }
192
193   /**
194    * GUI state
195    * 
196    * @param b
197    *          enable conservation based shading
198    */
199   public void setConservationSelected(boolean b)
200   {
201     conservationColourSelected = b;
202   }
203
204   @Override
205   public void setGlobalColourScheme(ColourSchemeI cs)
206   {
207     // TODO: logic refactored from AlignFrame changeColour -
208     // autorecalc stuff should be changed to rely on the worker system
209     // check to see if we should implement a changeColour(cs) method rather than
210     // put th logic in here
211     // - means that caller decides if they want to just modify state and defer
212     // calculation till later or to do all calculations in thread.
213     // via changecolour
214     globalColourScheme = cs;
215     boolean recalc = false;
216     if (cs != null)
217     {
218       cs.setConservationApplied(recalc = getConservationSelected());
219       if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
220               || cs instanceof Blosum62ColourScheme)
221       {
222         recalc = true;
223         cs.setThreshold(threshold, ignoreGapsInConsensusCalculation);
224       }
225       else
226       {
227         cs.setThreshold(0, ignoreGapsInConsensusCalculation);
228       }
229       if (recalc)
230       {
231         cs.setConsensus(hconsensus);
232         cs.setConservation(hconservation);
233       }
234       cs.alignmentChanged(alignment, hiddenRepSequences);
235     }
236     if (getColourAppliesToAllGroups())
237     {
238       for (SequenceGroup sg : getAlignment().getGroups())
239       {
240         if (cs == null)
241         {
242           sg.cs = null;
243           continue;
244         }
245         sg.cs = cs.applyTo(sg, getHiddenRepSequences());
246         sg.setConsPercGaps(ConsPercGaps);
247         if (getAbovePIDThreshold() || cs instanceof PIDColourScheme
248                 || cs instanceof Blosum62ColourScheme)
249         {
250           sg.cs.setThreshold(threshold, getIgnoreGapsConsensus());
251           recalc = true;
252         }
253         else
254         {
255           sg.cs.setThreshold(0, getIgnoreGapsConsensus());
256         }
257
258         if (getConservationSelected())
259         {
260           sg.cs.setConservationApplied(true);
261           recalc = true;
262         }
263         else
264         {
265           sg.cs.setConservation(null);
266           // sg.cs.setThreshold(0, getIgnoreGapsConsensus());
267         }
268         if (recalc)
269         {
270           sg.recalcConservation();
271         }
272         else
273         {
274           sg.cs.alignmentChanged(sg, hiddenRepSequences);
275         }
276       }
277     }
278
279   }
280
281   @Override
282   public ColourSchemeI getGlobalColourScheme()
283   {
284     return globalColourScheme;
285   }
286
287   protected AlignmentAnnotation consensus;
288
289   protected AlignmentAnnotation strucConsensus;
290
291   protected AlignmentAnnotation conservation;
292
293   protected AlignmentAnnotation quality;
294
295   protected AlignmentAnnotation[] groupConsensus;
296
297   protected AlignmentAnnotation[] groupConservation;
298
299   /**
300    * results of alignment consensus analysis for visible portion of view
301    */
302   protected Hashtable[] hconsensus = null;
303
304   /**
305    * results of secondary structure base pair consensus for visible portion of
306    * view
307    */
308   protected Hashtable[] hStrucConsensus = null;
309
310   protected Conservation hconservation = null;
311
312   @Override
313   public void setConservation(Conservation cons)
314   {
315     hconservation = cons;
316   }
317
318   /**
319    * percentage gaps allowed in a column before all amino acid properties should
320    * be considered unconserved
321    */
322   int ConsPercGaps = 25; // JBPNote : This should be a scalable property!
323
324   @Override
325   public int getConsPercGaps()
326   {
327     return ConsPercGaps;
328   }
329
330   @Override
331   public void setSequenceConsensusHash(Hashtable[] hconsensus)
332   {
333     this.hconsensus = hconsensus;
334
335   }
336
337   @Override
338   public Hashtable[] getSequenceConsensusHash()
339   {
340     return hconsensus;
341   }
342
343   @Override
344   public Hashtable[] getRnaStructureConsensusHash()
345   {
346     return hStrucConsensus;
347   }
348
349   @Override
350   public void setRnaStructureConsensusHash(Hashtable[] hStrucConsensus)
351   {
352     this.hStrucConsensus = hStrucConsensus;
353
354   }
355
356   @Override
357   public AlignmentAnnotation getAlignmentQualityAnnot()
358   {
359     return quality;
360   }
361
362   @Override
363   public AlignmentAnnotation getAlignmentConservationAnnotation()
364   {
365     return conservation;
366   }
367
368   @Override
369   public AlignmentAnnotation getAlignmentConsensusAnnotation()
370   {
371     return consensus;
372   }
373
374   @Override
375   public AlignmentAnnotation getAlignmentStrucConsensusAnnotation()
376   {
377     return strucConsensus;
378   }
379
380   protected AlignCalcManagerI calculator = new AlignCalcManager();
381
382   /**
383    * trigger update of conservation annotation
384    */
385   public void updateConservation(final AlignmentViewPanel ap)
386   {
387     // see note in mantis : issue number 8585
388     if (alignment.isNucleotide() || conservation == null
389             || !autoCalculateConsensus)
390     {
391       return;
392     }
393     if (calculator
394             .getRegisteredWorkersOfClass(jalview.workers.ConservationThread.class) == null)
395     {
396       calculator.registerWorker(new jalview.workers.ConservationThread(
397               this, ap));
398     }
399   }
400
401   /**
402    * trigger update of consensus annotation
403    */
404   public void updateConsensus(final AlignmentViewPanel ap)
405   {
406     // see note in mantis : issue number 8585
407     if (consensus == null || !autoCalculateConsensus)
408     {
409       return;
410     }
411     if (calculator.getRegisteredWorkersOfClass(ConsensusThread.class) == null)
412     {
413       calculator.registerWorker(new ConsensusThread(this, ap));
414     }
415   }
416
417   // --------START Structure Conservation
418   public void updateStrucConsensus(final AlignmentViewPanel ap)
419   {
420     if (autoCalculateStrucConsensus && strucConsensus == null
421             && alignment.isNucleotide() && alignment.hasRNAStructure())
422     {
423       // secondary structure has been added - so init the consensus line
424       initRNAStructure();
425     }
426
427     // see note in mantis : issue number 8585
428     if (strucConsensus == null || !autoCalculateStrucConsensus)
429     {
430       return;
431     }
432     if (calculator.getRegisteredWorkersOfClass(StrucConsensusThread.class) == null)
433     {
434       calculator.registerWorker(new StrucConsensusThread(this, ap));
435     }
436   }
437
438   public boolean isCalcInProgress()
439   {
440     return calculator.isWorking();
441   }
442
443   @Override
444   public boolean isCalculationInProgress(
445           AlignmentAnnotation alignmentAnnotation)
446   {
447     if (!alignmentAnnotation.autoCalculated)
448     {
449       return false;
450     }
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       List<SequenceI> tmp = alignment.getHiddenSequences().showAll(
903               hiddenRepSequences);
904       for (SequenceI seq : tmp)
905       {
906         selectionGroup.addSequence(seq, false);
907         setSequenceAnnotationsVisible(seq, true);
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     List<SequenceI> tmp = alignment.getHiddenSequences().showSequence(
923             index,
924             hiddenRepSequences);
925     if (tmp.size() > 0)
926     {
927       if (selectionGroup == null)
928       {
929         selectionGroup = new SequenceGroup();
930         selectionGroup.setEndRes(alignment.getWidth() - 1);
931       }
932
933       for (SequenceI seq : tmp)
934       {
935         selectionGroup.addSequence(seq, false);
936         setSequenceAnnotationsVisible(seq, true);
937       }
938       // JBPNote: refactor: only update flag if we modified visiblity (used to
939       // do this regardless)
940       if (alignment.getHiddenSequences().getSize() < 1)
941       {
942         hasHiddenRows = false;
943       }
944       firePropertyChange("alignment", null, alignment.getSequences());
945       sendSelection();
946     }
947   }
948
949   public void hideAllSelectedSeqs()
950   {
951     if (selectionGroup == null || selectionGroup.getSize() < 1)
952     {
953       return;
954     }
955
956     SequenceI[] seqs = selectionGroup.getSequencesInOrder(alignment);
957
958     hideSequence(seqs);
959
960     setSelectionGroup(null);
961   }
962
963   public void hideSequence(SequenceI[] seq)
964   {
965     if (seq != null)
966     {
967       for (int i = 0; i < seq.length; i++)
968       {
969         alignment.getHiddenSequences().hideSequence(seq[i]);
970         setSequenceAnnotationsVisible(seq[i], false);
971       }
972       hasHiddenRows = true;
973       firePropertyChange("alignment", null, alignment.getSequences());
974     }
975   }
976
977   /**
978    * Set visibility for any annotations for the given sequence.
979    * 
980    * @param sequenceI
981    */
982   protected void setSequenceAnnotationsVisible(SequenceI sequenceI,
983           boolean visible)
984   {
985     for (AlignmentAnnotation ann : alignment.getAlignmentAnnotation())
986     {
987       if (ann.sequenceRef == sequenceI)
988       {
989         ann.visible = visible;
990       }
991     }
992   }
993
994   public void hideRepSequences(SequenceI repSequence, SequenceGroup sg)
995   {
996     int sSize = sg.getSize();
997     if (sSize < 2)
998     {
999       return;
1000     }
1001
1002     if (hiddenRepSequences == null)
1003     {
1004       hiddenRepSequences = new Hashtable();
1005     }
1006
1007     hiddenRepSequences.put(repSequence, sg);
1008
1009     // Hide all sequences except the repSequence
1010     SequenceI[] seqs = new SequenceI[sSize - 1];
1011     int index = 0;
1012     for (int i = 0; i < sSize; i++)
1013     {
1014       if (sg.getSequenceAt(i) != repSequence)
1015       {
1016         if (index == sSize - 1)
1017         {
1018           return;
1019         }
1020
1021         seqs[index++] = sg.getSequenceAt(i);
1022       }
1023     }
1024     sg.setSeqrep(repSequence); // note: not done in 2.7applet
1025     sg.setHidereps(true); // note: not done in 2.7applet
1026     hideSequence(seqs);
1027
1028   }
1029
1030   public boolean isHiddenRepSequence(SequenceI seq)
1031   {
1032     return hiddenRepSequences != null
1033             && hiddenRepSequences.containsKey(seq);
1034   }
1035
1036   public SequenceGroup getRepresentedSequences(SequenceI seq)
1037   {
1038     return (SequenceGroup) (hiddenRepSequences == null ? null
1039             : hiddenRepSequences.get(seq));
1040   }
1041
1042   public int adjustForHiddenSeqs(int alignmentIndex)
1043   {
1044     return alignment.getHiddenSequences().adjustForHiddenSeqs(
1045             alignmentIndex);
1046   }
1047
1048   // Selection manipulation
1049   /**
1050    * broadcast selection to any interested parties
1051    */
1052   public abstract void sendSelection();
1053
1054   public void invertColumnSelection()
1055   {
1056     colSel.invertColumnSelection(0, alignment.getWidth());
1057   }
1058
1059   /**
1060    * This method returns an array of new SequenceI objects derived from the
1061    * whole alignment or just the current selection with start and end points
1062    * adjusted
1063    * 
1064    * @note if you need references to the actual SequenceI objects in the
1065    *       alignment or currently selected then use getSequenceSelection()
1066    * @return selection as new sequenceI objects
1067    */
1068   public SequenceI[] getSelectionAsNewSequence()
1069   {
1070     SequenceI[] sequences;
1071     // JBPNote: Need to test jalviewLite.getSelectedSequencesAsAlignmentFrom -
1072     // this was the only caller in the applet for this method
1073     // JBPNote: in applet, this method returned references to the alignment
1074     // sequences, and it did not honour the presence/absence of annotation
1075     // attached to the alignment (probably!)
1076     if (selectionGroup == null || selectionGroup.getSize() == 0)
1077     {
1078       sequences = alignment.getSequencesArray();
1079       AlignmentAnnotation[] annots = alignment.getAlignmentAnnotation();
1080       for (int i = 0; i < sequences.length; i++)
1081       {
1082         sequences[i] = new Sequence(sequences[i], annots); // construct new
1083         // sequence with
1084         // subset of visible
1085         // annotation
1086       }
1087     }
1088     else
1089     {
1090       sequences = selectionGroup.getSelectionAsNewSequences(alignment);
1091     }
1092
1093     return sequences;
1094   }
1095
1096   /**
1097    * get the currently selected sequence objects or all the sequences in the
1098    * alignment.
1099    * 
1100    * @return array of references to sequence objects
1101    */
1102   @Override
1103   public SequenceI[] getSequenceSelection()
1104   {
1105     SequenceI[] sequences = null;
1106     if (selectionGroup != null)
1107     {
1108       sequences = selectionGroup.getSequencesInOrder(alignment);
1109     }
1110     if (sequences == null)
1111     {
1112       sequences = alignment.getSequencesArray();
1113     }
1114     return sequences;
1115   }
1116
1117   /**
1118    * This method returns the visible alignment as text, as seen on the GUI, ie
1119    * if columns are hidden they will not be returned in the result. Use this for
1120    * calculating trees, PCA, redundancy etc on views which contain hidden
1121    * columns.
1122    * 
1123    * @return String[]
1124    */
1125   @Override
1126   public jalview.datamodel.CigarArray getViewAsCigars(
1127           boolean selectedRegionOnly)
1128   {
1129     return new jalview.datamodel.CigarArray(alignment,
1130             (hasHiddenColumns ? colSel : null),
1131             (selectedRegionOnly ? selectionGroup : null));
1132   }
1133
1134   /**
1135    * return a compact representation of the current alignment selection to pass
1136    * to an analysis function
1137    * 
1138    * @param selectedOnly
1139    *          boolean true to just return the selected view
1140    * @return AlignmentView
1141    */
1142   @Override
1143   public jalview.datamodel.AlignmentView getAlignmentView(
1144           boolean selectedOnly)
1145   {
1146     return getAlignmentView(selectedOnly, false);
1147   }
1148
1149   /**
1150    * return a compact representation of the current alignment selection to pass
1151    * to an analysis function
1152    * 
1153    * @param selectedOnly
1154    *          boolean true to just return the selected view
1155    * @param markGroups
1156    *          boolean true to annotate the alignment view with groups on the
1157    *          alignment (and intersecting with selected region if selectedOnly
1158    *          is true)
1159    * @return AlignmentView
1160    */
1161   @Override
1162   public jalview.datamodel.AlignmentView getAlignmentView(
1163           boolean selectedOnly, boolean markGroups)
1164   {
1165     return new AlignmentView(alignment, colSel, selectionGroup,
1166             hasHiddenColumns, selectedOnly, markGroups);
1167   }
1168
1169   /**
1170    * This method returns the visible alignment as text, as seen on the GUI, ie
1171    * if columns are hidden they will not be returned in the result. Use this for
1172    * calculating trees, PCA, redundancy etc on views which contain hidden
1173    * columns.
1174    * 
1175    * @return String[]
1176    */
1177   @Override
1178   public String[] getViewAsString(boolean selectedRegionOnly)
1179   {
1180     String[] selection = null;
1181     SequenceI[] seqs = null;
1182     int i, iSize;
1183     int start = 0, end = 0;
1184     if (selectedRegionOnly && selectionGroup != null)
1185     {
1186       iSize = selectionGroup.getSize();
1187       seqs = selectionGroup.getSequencesInOrder(alignment);
1188       start = selectionGroup.getStartRes();
1189       end = selectionGroup.getEndRes() + 1;
1190     }
1191     else
1192     {
1193       iSize = alignment.getHeight();
1194       seqs = alignment.getSequencesArray();
1195       end = alignment.getWidth();
1196     }
1197
1198     selection = new String[iSize];
1199     if (hasHiddenColumns)
1200     {
1201       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
1202     }
1203     else
1204     {
1205       for (i = 0; i < iSize; i++)
1206       {
1207         selection[i] = seqs[i].getSequenceAsString(start, end);
1208       }
1209
1210     }
1211     return selection;
1212   }
1213
1214   /**
1215    * return visible region boundaries within given column range
1216    * 
1217    * @param min
1218    *          first column (inclusive, from 0)
1219    * @param max
1220    *          last column (exclusive)
1221    * @return int[][] range of {start,end} visible positions
1222    */
1223   public int[][] getVisibleRegionBoundaries(int min, int max)
1224   {
1225     Vector regions = new Vector();
1226     int start = min;
1227     int end = max;
1228
1229     do
1230     {
1231       if (hasHiddenColumns)
1232       {
1233         if (start == 0)
1234         {
1235           start = colSel.adjustForHiddenColumns(start);
1236         }
1237
1238         end = colSel.getHiddenBoundaryRight(start);
1239         if (start == end)
1240         {
1241           end = max;
1242         }
1243         if (end > max)
1244         {
1245           end = max;
1246         }
1247       }
1248
1249       regions.addElement(new int[]
1250       { start, end });
1251
1252       if (hasHiddenColumns)
1253       {
1254         start = colSel.adjustForHiddenColumns(end);
1255         start = colSel.getHiddenBoundaryLeft(start) + 1;
1256       }
1257     } while (end < max);
1258
1259     int[][] startEnd = new int[regions.size()][2];
1260
1261     regions.copyInto(startEnd);
1262
1263     return startEnd;
1264
1265   }
1266
1267   @Override
1268   public List<AlignmentAnnotation> getVisibleAlignmentAnnotation(boolean selectedOnly)
1269   {
1270     ArrayList<AlignmentAnnotation> ala = new ArrayList<AlignmentAnnotation>();
1271     AlignmentAnnotation[] aa;
1272     if ((aa=alignment.getAlignmentAnnotation())!=null)
1273     {
1274       for (AlignmentAnnotation annot:aa)
1275       {
1276         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
1277         if (selectedOnly && selectionGroup!=null)
1278         {
1279           colSel.makeVisibleAnnotation(selectionGroup.getStartRes(), selectionGroup.getEndRes(),clone);
1280         } else {
1281           colSel.makeVisibleAnnotation(clone);
1282         }
1283         ala.add(clone);
1284       }
1285     }
1286     return ala;
1287   }
1288
1289   /**
1290    * @return the padGaps
1291    */
1292   public boolean isPadGaps()
1293   {
1294     return padGaps;
1295   }
1296
1297   /**
1298    * @param padGaps
1299    *          the padGaps to set
1300    */
1301   public void setPadGaps(boolean padGaps)
1302   {
1303     this.padGaps = padGaps;
1304   }
1305
1306   /**
1307    * apply any post-edit constraints and trigger any calculations needed after
1308    * an edit has been performed on the alignment
1309    * 
1310    * @param ap
1311    */
1312   public void alignmentChanged(AlignmentViewPanel ap)
1313   {
1314     if (isPadGaps())
1315     {
1316       alignment.padGaps();
1317     }
1318     if (autoCalculateConsensus)
1319     {
1320       updateConsensus(ap);
1321     }
1322     if (hconsensus != null && autoCalculateConsensus)
1323     {
1324       updateConservation(ap);
1325     }
1326     if (autoCalculateStrucConsensus)
1327     {
1328       updateStrucConsensus(ap);
1329     }
1330
1331     // Reset endRes of groups if beyond alignment width
1332     int alWidth = alignment.getWidth();
1333     List<SequenceGroup> groups = alignment.getGroups();
1334     if (groups != null)
1335     {
1336       for (SequenceGroup sg : groups)
1337       {
1338         if (sg.getEndRes() > alWidth)
1339         {
1340           sg.setEndRes(alWidth - 1);
1341         }
1342       }
1343     }
1344
1345     if (selectionGroup != null && selectionGroup.getEndRes() > alWidth)
1346     {
1347       selectionGroup.setEndRes(alWidth - 1);
1348     }
1349
1350     resetAllColourSchemes();
1351     calculator.restartWorkers();
1352     // alignment.adjustSequenceAnnotations();
1353   }
1354
1355   /**
1356    * reset scope and do calculations for all applied colourschemes on alignment
1357    */
1358   void resetAllColourSchemes()
1359   {
1360     ColourSchemeI cs = globalColourScheme;
1361     if (cs != null)
1362     {
1363       cs.alignmentChanged(alignment, hiddenRepSequences);
1364
1365       cs.setConsensus(hconsensus);
1366       if (cs.conservationApplied())
1367       {
1368         cs.setConservation(Conservation.calculateConservation("All",
1369                 ResidueProperties.propHash, 3, alignment.getSequences(), 0,
1370                 alignment.getWidth(), false, getConsPercGaps(), false));
1371       }
1372     }
1373
1374     for (SequenceGroup sg : alignment.getGroups())
1375     {
1376       if (sg.cs != null)
1377       {
1378         sg.cs.alignmentChanged(sg, hiddenRepSequences);
1379       }
1380       sg.recalcConservation();
1381     }
1382   }
1383
1384   protected void initAutoAnnotation()
1385   {
1386     // TODO: add menu option action that nulls or creates consensus object
1387     // depending on if the user wants to see the annotation or not in a
1388     // specific alignment
1389
1390     if (hconsensus == null && !isDataset)
1391     {
1392       if (!alignment.isNucleotide())
1393       {
1394         initConservation();
1395         initQuality();
1396       }
1397       else
1398       {
1399         initRNAStructure();
1400       }
1401       initConsensus();
1402     }
1403   }
1404
1405   private void initConsensus()
1406   {
1407
1408     consensus = new AlignmentAnnotation("Consensus", "PID",
1409             new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1410     consensus.hasText = true;
1411     consensus.autoCalculated = true;
1412
1413     if (showConsensus)
1414     {
1415       alignment.addAnnotation(consensus);
1416     }
1417   }
1418
1419   private void initConservation()
1420   {
1421     if (showConservation)
1422     {
1423       if (conservation == null)
1424       {
1425         conservation = new AlignmentAnnotation("Conservation",
1426                 "Conservation of total alignment less than "
1427                         + getConsPercGaps() + "% gaps", new Annotation[1],
1428                 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1429         conservation.hasText = true;
1430         conservation.autoCalculated = true;
1431         alignment.addAnnotation(conservation);
1432       }
1433     }
1434   }
1435
1436   private void initQuality()
1437   {
1438     if (showQuality)
1439     {
1440       if (quality == null)
1441       {
1442         quality = new AlignmentAnnotation("Quality",
1443                 "Alignment Quality based on Blosum62 scores",
1444                 new Annotation[1], 0f, 11f, AlignmentAnnotation.BAR_GRAPH);
1445         quality.hasText = true;
1446         quality.autoCalculated = true;
1447         alignment.addAnnotation(quality);
1448       }
1449     }
1450   }
1451
1452   private void initRNAStructure()
1453   {
1454     if (alignment.hasRNAStructure() && strucConsensus == null)
1455     {
1456       strucConsensus = new AlignmentAnnotation("StrucConsensus", "PID",
1457               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
1458       strucConsensus.hasText = true;
1459       strucConsensus.autoCalculated = true;
1460
1461       if (showConsensus)
1462       {
1463         alignment.addAnnotation(strucConsensus);
1464       }
1465     }
1466   }
1467
1468   /*
1469    * (non-Javadoc)
1470    * 
1471    * @see jalview.api.AlignViewportI#calcPanelHeight()
1472    */
1473   public int calcPanelHeight()
1474   {
1475     // setHeight of panels
1476     AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
1477     int height = 0;
1478     int charHeight = getCharHeight();
1479     if (aa != null)
1480     {
1481       BitSet graphgrp = new BitSet();
1482       for (int i = 0; i < aa.length; i++)
1483       {
1484         if (aa[i] == null)
1485         {
1486           System.err.println("Null annotation row: ignoring.");
1487           continue;
1488         }
1489         if (!aa[i].visible)
1490         {
1491           continue;
1492         }
1493         if (aa[i].graphGroup > -1)
1494         {
1495           if (graphgrp.get(aa[i].graphGroup))
1496           {
1497             continue;
1498           }
1499           else
1500           {
1501             graphgrp.set(aa[i].graphGroup);
1502           }
1503         }
1504         aa[i].height = 0;
1505
1506         if (aa[i].hasText)
1507         {
1508           aa[i].height += charHeight;
1509         }
1510
1511         if (aa[i].hasIcons)
1512         {
1513           aa[i].height += 16;
1514         }
1515
1516         if (aa[i].graph > 0)
1517         {
1518           aa[i].height += aa[i].graphHeight;
1519         }
1520
1521         if (aa[i].height == 0)
1522         {
1523           aa[i].height = 20;
1524         }
1525
1526         height += aa[i].height;
1527       }
1528     }
1529     if (height == 0)
1530     {
1531       // set minimum
1532       height = 20;
1533     }
1534     return height;
1535   }
1536
1537   @Override
1538   public void updateGroupAnnotationSettings(boolean applyGlobalSettings,
1539           boolean preserveNewGroupSettings)
1540   {
1541     boolean updateCalcs = false;
1542     boolean conv = isShowGroupConservation();
1543     boolean cons = isShowGroupConsensus();
1544     boolean showprf = isShowSequenceLogo();
1545     boolean showConsHist = isShowConsensusHistogram();
1546     boolean normLogo = isNormaliseSequenceLogo();
1547
1548     /**
1549      * TODO reorder the annotation rows according to group/sequence ordering on
1550      * alignment
1551      */
1552     boolean sortg = true;
1553
1554     // remove old automatic annotation
1555     // add any new annotation
1556
1557     // intersect alignment annotation with alignment groups
1558
1559     AlignmentAnnotation[] aan = alignment.getAlignmentAnnotation();
1560     List<SequenceGroup> oldrfs = new ArrayList<SequenceGroup>();
1561     if (aan != null)
1562     {
1563       for (int an = 0; an < aan.length; an++)
1564       {
1565         if (aan[an].autoCalculated && aan[an].groupRef != null)
1566         {
1567           oldrfs.add(aan[an].groupRef);
1568           alignment.deleteAnnotation(aan[an], false);
1569         }
1570       }
1571     }
1572     if (alignment.getGroups() != null)
1573     {
1574       for (SequenceGroup sg : alignment.getGroups())
1575       {
1576         updateCalcs = false;
1577         if (applyGlobalSettings
1578                 || (!preserveNewGroupSettings && !oldrfs.contains(sg)))
1579         {
1580           // set defaults for this group's conservation/consensus
1581           sg.setshowSequenceLogo(showprf);
1582           sg.setShowConsensusHistogram(showConsHist);
1583           sg.setNormaliseSequenceLogo(normLogo);
1584         }
1585         if (conv)
1586         {
1587           updateCalcs = true;
1588           alignment.addAnnotation(sg.getConservationRow(), 0);
1589         }
1590         if (cons)
1591         {
1592           updateCalcs = true;
1593           alignment.addAnnotation(sg.getConsensus(), 0);
1594         }
1595         // refresh the annotation rows
1596         if (updateCalcs)
1597         {
1598           sg.recalcConservation();
1599         }
1600       }
1601     }
1602     oldrfs.clear();
1603   }
1604
1605   @Override
1606   public Color getSequenceColour(SequenceI seq)
1607   {
1608     Color sqc = Color.white;
1609     if (sequenceColours != null)
1610     {
1611       sqc = (Color) sequenceColours.get(seq);
1612       if (sqc == null)
1613       {
1614         sqc = Color.white;
1615       }
1616     }
1617     return sqc;
1618   }
1619
1620   @Override
1621   public void setSequenceColour(SequenceI seq, Color col)
1622   {
1623     if (sequenceColours == null)
1624     {
1625       sequenceColours = new Hashtable();
1626     }
1627
1628     if (col == null)
1629     {
1630       sequenceColours.remove(seq);
1631     }
1632     else
1633     {
1634       sequenceColours.put(seq, col);
1635     }
1636   }
1637
1638   @Override
1639   public void updateSequenceIdColours()
1640   {
1641     if (sequenceColours == null)
1642     {
1643       sequenceColours = new Hashtable();
1644     }
1645     for (SequenceGroup sg : alignment.getGroups())
1646     {
1647       if (sg.idColour != null)
1648       {
1649         for (SequenceI s : sg.getSequences(getHiddenRepSequences()))
1650         {
1651           sequenceColours.put(s, sg.idColour);
1652         }
1653       }
1654     }
1655   }
1656
1657   @Override
1658   public void clearSequenceColours()
1659   {
1660     sequenceColours = null;
1661   };
1662 }