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