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