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