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