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